MyBatis缓存的底层原理(源码分析)
在分析之前,先明确两个要点
- 缓存的执行顺序: 二级缓存 —> 一级缓存 —> 数据库
①到二级缓存中去找,因为二级缓存中可能有其它SqlSession的缓存
②如果二级缓存没找到,就到一级缓存中找
③一级缓存中没找到就到数据库中查 - 缓存的实质:缓存的实质就是一个Map,key对应一个具体的缓存标识,value为缓存的内容。
一、Cache接口及其实现类
org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认实现,PerpetualCache会同时操作Mybatis的一级缓存和二级缓存的数据,它通过两个不同的调用者分别实现一级缓存和二级缓存使用同一个实现处理
- 一级缓存: BaseExecutor调用PerpetualCache
- 二级缓存: CachingExecutor调用PerpetualCache
二、一级缓存
1、进入二级缓存中查找
CachingExecutor类
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();//查看二级缓存是否有数据
if (cache != null) {//如果有数据,则进入查找是否有匹配的缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//因为缓存的实质就是一个Map,所以拿到key,到cache中找目标缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果没找到就到一级缓存中去找,调用query方法
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);
}
2、如果二级缓存没有命中,则进入一级缓存中寻找
BaseExecutor类
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
/*
判断结果集是否为null,如果为null,则到loaclCache(一级缓存数据存放位置)通过key去找去找
如果缓存中有,则赋给list
*/
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {//判断是否命中缓存,如果!=null则为命中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {//如果未命中,则从数据库中查找
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
三、二级缓存
说明: 二级缓存执行流程与一级缓存一致,但使用Mybatis与EHCache二级缓存的内容不一样,EHCache内容会更丰富,健壮性稍强。
附加:对比Mybatis自带的二级缓存与EHCache缓存对象的结构对比
【1】Mybatis二级缓存
【2】EHCache二级缓存
总结
缓存的实质就是一个Map,key对应一个具体的缓存标识,value为缓存的内容。执行查询时会先经过
CachingExecutor类中的query方法,查看二级缓存中是否有对应的缓存,如果没有再进入一级缓存中查找,
如果一级缓存中也没有,最后到数据库中查。