短视频整套源码,针对二级缓存的源码进行分析
本小节将对二级缓存对应的Mybatis源码进行讨论。Mybatis中开启二级缓存之后,执行查询操作时,调用链如下所示。
在CachingExecutor中有两个重载的query()方法,下面先看第一个query()方法,如下所示。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 获取Sql语句BoundSql boundSql = ms.getBoundSql(parameterObject);// 创建CacheKeyCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
继续看重载的query()方法,如下所示。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 从MappedStatement中将二级缓存获取出来Cache cache = ms.getCache();if (cache != null) {// 清空二级缓存(如果需要的话)flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// 处理存储过程相关逻辑ensureNoOutParams(ms, boundSql);// 从二级缓存中根据CacheKey命中查询结果List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// 未命中缓存,则查数据库list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将从数据库查询到的结果缓存到二级缓存中tcm.putObject(cache, key, list);}// 返回查询结果return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上述query()方法整体执行流程比较简单,概括下来就是:先从缓存中命中查询结果,命中到查询结果则返回,未命中到查询结果则直接查询数据库并把查询结果缓存到二级缓存中。但是从二级缓存中根据CacheKey命中查询结果时,并没有直接通过Cache的getObject()方法,而是通过tcm的getObject()方法,合理进行推测的话,应该就是tcm持有二级缓存的引用,当需要从二级缓存中命中查询结果时,由tcm将请求转发给二级缓存。实际上,tcm为CachingExecutor持有的TransactionalCacheManager对象,从二级缓存中命中查询结果这一请求之所以需要通过TransactionalCacheManager转发给二级缓存,是因为需要借助TransactionalCacheManager实现只有当事务提交时,二级缓存才会被更新这一功能。联想到第三小节中的场景一和场景二的示例,将查询结果缓存到二级缓存中需要事务提交这一功能,其实就是借助TransactionalCacheManager实现的,所以下面对TransactionalCacheManager进行一个说明。首先TransactionalCacheManager的类图如下所示。
TransactionalCacheManager中持有一个Map,该Map的键为Cache,值为TransactionalCache,即一个二级缓存对应一个TransactionalCache。继续看TransactionalCacheManager的getObject()方法,如下所示。
public Object getObject(Cache cache, CacheKey key) {return getTransactionalCache(cache).getObject(key);
}private TransactionalCache getTransactionalCache(Cache cache) {return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
通过上述代码可以知道,一个二级缓存对应一个TransactionalCache,且TransactionalCache中持有这个二级缓存的引用,当调用TransactionalCacheManager的getObject()方法时,TransactionalCacheManager会将调用请求转发给TransactionalCache,下面分析一下TransactionalCache,类图如下所示。
继续看TransactionalCache的getObject()方法,如下所示。
@Override
public Object getObject(Object key) {// 在二级缓存中命中查询结果Object object = delegate.getObject(key);if (object == null) {// 未命中则将CacheKey添加到entriesMissedInCache中// 用于统计命中率entriesMissedInCache.add(key);}if (clearOnCommit) {return null;} else {return object;}
}
到这里就可以知道了,在CachingExecutor中通过CacheKey命中查询结果时,其实就是CachingExecutor将请求发送给TransactionalCacheManager,TransactionalCacheManager将请求转发给二级缓存对应的TransactionalCache,最后再由TransactionalCache将请求最终传递到二级缓存。在上述getObject()方法中,如果clearOnCommit为true,则无论是否在二级缓存中命中查询结果,均返回null,那么clearOnCommit在什么地方会被置为true呢,其实就是在CachingExecutor的flushCacheIfRequired()方法中,这个方法在上面分析的query()方法中会被调用到,看一下flushCacheIfRequired()的实现,如下所示。
private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();if (cache != null && ms.isFlushCacheRequired()) {tcm.clear(cache);}
}
调用TransactionalCacheManager的clear()方法时,最终会调用到TransactionalCache的clear()方法,如下所示。
@Override
public void clear() {clearOnCommit = true;entriesToAddOnCommit.clear();
}
现在继续分析为什么将查询结果缓存到二级缓存中需要事务提交。从数据库中查询出来结果后,CachingExecutor会调用TransactionalCacheManager的putObject()方法试图将查询结果缓存到二级缓存中,我们已经知道,如果事务不提交,那么查询结果是无法被缓存到二级缓存中,那么在事务提交之前,查询结果肯定被暂存到了某个地方,为了搞清楚这部分逻辑,先看一下TransactionalCacheManager的putObject()方法,如下所示。
public void putObject(Cache cache, CacheKey key, Object value) {getTransactionalCache(cache).putObject(key, value);
}
继续看TransactionalCache的putObject()方法,如下所示。
@Override
public void putObject(Object key, Object object) {entriesToAddOnCommit.put(key, object);
}
到这里就搞明白了,在事务提交之前,查询结果会被暂存到TransactionalCache的entriesToAddOnCommit中。下面继续分析事务提交时如何将entriesToAddOnCommit中暂存的查询结果刷新到二级缓存中,DefaultSqlSession的commit()方法如下所示。
@Override
public void commit() {commit(false);
}@Override
public void commit(boolean force) {try {executor.commit(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}
在DefaultSqlSession的commit()方法中会调用到CachingExecutor的commit()方法,如下所示。
@Override
public void commit(boolean required) throws SQLException {delegate.commit(required);// 调用TransactionalCacheManager的commit()方法tcm.commit();
}
在CachingExecutor的commit()方法中,会调用TransactionalCacheManager的commit()方法,如下所示。
public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {// 调用TransactionalCache的commit()方法txCache.commit();}
}
继续看TransactionalCache的commit()方法,如下所示。
public void commit() {if (clearOnCommit) {delegate.clear();}flushPendingEntries();reset();
}private void flushPendingEntries() {// 将entriesToAddOnCommit中暂存的查询结果全部缓存到二级缓存中for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {delegate.putObject(entry.getKey(), entry.getValue());}for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {delegate.putObject(entry, null);}}
}
至此可以知道,当调用SqlSession的commit()方法时,会一路传递到TransactionalCache的commit()方法,最终调用TransactionalCache的flushPendingEntries()方法将暂存的查询结果全部刷到二级缓存中。
当执行增,删,改操作并提交事务时,二级缓存会被清空,这是因为增,删,改操作最终会调用到CachingExecutor的update()方法,而update()方法中又会调用flushCacheIfRequired()方法,已经知道在flushCacheIfRequired()方法中如果所执行的方法对应的MappedStatement的flushCacheRequired字段为true的话,则会最终将TransactionalCache中的clearOnCommit字段置为true,随即在事务提交的时候,会将二级缓存清空。而加载映射文件时,解析CURD标签为MappedStatement时有如下一行代码。
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
即如果没有在CURD标签中显式的设置flushCache属性,则会给flushCache字段一个默认值,且默认值为非查询标签下默认为true,所以到这里就可以知道,如果是增,删,改操作,那么TransactionalCache中的clearOnCommit字段会被置为true,从而在提交事务时会在TransactionalCache的commit()方法中将二级缓存清空。
到这里,二级缓存的源码分析结束。二级缓存的使用流程可以用下图进行概括,如下所示。
以上就是短视频整套源码,针对二级缓存的源码进行分析, 更多内容欢迎关注之后的文章