Mybatis缓存体系:二级缓存
一、概要
前面Mybatis是应用级别的缓存,执行查询是先查询二级,没有命中才查询一级缓存,否则查询数据库。
值得注意的是,不同于一级缓存,因为二级缓存是作用于应用的,那么就存在一个脏读的问题,所以就引入了事务缓存。
二、使用
在Mapper映射文件里配置<cache></cache>即可,那么整个mapper下就会启用二级缓存。也可以粒度控制到方法,比如在配置useCache为false,那么该方法就不会使用二级缓存。另外二级缓存是作用与nameSpace的,如果是关联查询,需要另一个namespece的二级缓存,可以<cache-ref namespace="xxx.xxxMapper"/>。
三、命中条件
- 相同的statement id
- 相同的Sql与参数
- 没有使用ResultHandler来自定义返回数据
- 没有配置UseCache=false 来关闭缓存
- 没有配置FlushCache=true 来清空缓存在
四、源码解读
概念介绍
事物缓存管理器(TransactionalCacheManager)
二级缓存是在事务提交后才会写入,目的是为了防止其它会话脏读缓存。所以在话与二级缓存中间会有一个事物缓存管理器,会话其间查询的数据会放到管理器的暂存区。当事务提交后会才会写入指定二级缓存区域。管理器的生命周期与会话保持一至。
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
//放到暂存区
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
//提交到二级缓存
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
暂存区(TransactionalCache)
暂时存放待缓存的数据区域,和缓存区是一一对应的。如果会话会涉及多个二级缓存的访问,那么对应暂存区也会有多个。暂存区生命周期与会话保持一至。
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
..............................................
..............................................
}
缓存区
缓存区是通过Mapper声明而获得,默认每个Mapper都有独立的缓存区。其作用是真正存放数据和实现缓存的业务逻辑。如序列化、防止缓存穿透、缓存效期等。
执行流程
核心是CachingExecutor这个类(维护了二级缓存),它是对BaseExecutor(维护一级缓存)的装饰,执行sql时候,会先去CachingExecutor查询是否有二级缓存,如果没有再去BaseExecutor查询一级缓存。
接着我们就看.CachingExecutor#query()方法:
可以看到这里实际是放入到暂存区,并没有提交数据到二级缓存。
那么什么时候提交到二级缓存呢?
我们猜想一下应该是会话结束才提交事务,会话关闭就是DefaultSqlSession#close()方法,我们去验证一下:
又跳转到了CachingExecutor#close方法:
可以看到这里提交或者回滚了,验证是对的。