Mybatis作为一个强大的ORM框架,也提供了数据的缓存功能,缓存的架构分为两层:一级缓存和二级缓存。
一级缓存是会话级别的缓存,当SqlSessionFactory创建一个SqlSession对象时,就表示开启了一个数据库会话,同时也开启了这个会话的一级缓存。那么为什么要需要会话级别的缓存呢。在一个sqlSession中会存在多次的查询。相同的sql查询在应用一级缓存中有较高的效率。所以当sqlSession关闭时,一级缓存就会清空。
二级缓存是MapperStatement级别的缓存,也就是一个namespace一个二级缓存,而且二级缓存是通过CachingExecutor实现的。它的生命周期与应用程序的生命周期相同。
如果一级和二级的缓存都开启,那么sql先查询二级缓存,如果二级缓存没有命中,那么会去查询一级缓存。
按照sql的执行顺序来分析缓存:
二级缓存的执行是由CachingExecutor来执行的,那么CachingExecutor是怎么来的呢?
在DefaultSqlSessionFactory构建DefaultSqlSession时
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
当判断二级缓存cacheEnable为true,创建CachingExecutor来注入SqlSession中。
CachingExecutor类介绍:
private final Executor delegate;
//事务缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
TransactionalCacheManager类是用来维护Cache和TransactionalCache之间的映射关系的。在其内部便是维护了
private final Map transactionalCaches = new HashMap();
这样的关系Map。
//分析一个query的
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement中获取缓存
Cache cache = ms.getCache();
if (cache != null) {
// 如果配置文件中没有配置 ,则 cache 为空
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//查找二级缓存
List list = (List) tcm.getObject(cache, key);
if (list == null) {
//委托给BaseExcetor的子类来执行
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
一个经过二级缓存的查询大致的逻辑。当Mapper配置了
TransactionalCache类是缓存SqlSession的某个事务需要向某个二级缓存添加的缓存数据。
//属性
//底层封装的二级缓存所对应的Cache对象
private final Cache delegate;
//表示当前TransactionalCache不可查询,且提交事务会清空Cache.
private boolean clearOnCommit;
//暂时保存事务中的数据,事务提交后会将数据添加至二级缓存。
private final Map
添加/获取缓存记录:
public Object getObject(Object key) {
//查询二级缓存是否有指定的key
Object object = delegate.getObject(key);
if (object == null) {
//不存在,记录这个未命中key
entriesMissedInCache.add(key);
}
if (clearOnCommit) {
return null;
} else {
return object;
}
}
//缓存在entriesToAddOnCommit等待事务提交
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
//提交
public void commit() {
//事务提交前清空二级缓存
if (clearOnCommit) {
delegate.clear();
}
//将entriesToAddOnCommit添加至二级缓存中
flushPendingEntries();
//重置clearOnCommit=false,清空entriesToAddOnCommit、entriesMissedInCache
reset();
}
private void flushPendingEntries() {
//将entriesToAddOnCommit添加至二级缓存中
for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
//将未命中的缓存且不存在的置为null, 自己觉得是为了防止缓存穿透
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
那么为什么要当事务提交之后才去将数据缓存在二级缓存中。这是防止出现脏读的现象。
当有两个事务T1,T2。当T1添加A,之后查询A的记录最后提交事务。 T2查询记录A。 如果查询后直接提交给二级缓存,那么在T1未提交事务之前,T2边获取到了A,出现了脏读。所以最好的方式当事务提交之后,再将数据提交至二级缓存中。