一、二级缓存介绍
由上一篇文章,Mybatis的执行流程可知,CRUD实际上是交给Executor去处理的,如下图:
从图中可以看到Mybatis一二级缓存所在位置:一级缓存在BaseExecutor,二级缓存在CachingExecutor。
而BaseExecutor、CachingExecutor都是Executor的子类,且CachingExecutor包含BaseExecutor。直接看下CachingExecutor#query()的源码:
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")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 这里的delegate就是BaseExecutor
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
再看下BaseExecutor#query()的源码:
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++;
// 获取一级缓存,localCache就是一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != 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;
}
二、二级缓存的比较:
1、作用域不同:一级缓存的作用域是sqlSession,而二级缓存的作用域是Mapper。
2、缓存的key不同:虽然都是map结构,但是一级缓存的key根据sqlID来区分,value =查询结果;二级缓存的key根据Mapper.xml的命名空间来区分。
3、一二级缓存都有弊端:
一级缓存是每个SqlSession私有的,其他的SqlSession做了修改操作,当前的SqlSession并不会更新,会存在缓存一致性的问题。
二级缓存不同命名空间的修改可能会导致其他命名空间数据脏读:针对一个表的某些操作不在他独立的namespace下进行。由于不在同一个namespace,即不在同一个作用域,其中一个namespace进行了update操作,另一个namespace的缓存不会失效,会存在脏读。多表操作一定不能使用缓存!