Mybatis版本 3.4.6
配置
config配置
一级二级缓存都是默认有效的,除非使用上述配置显式关闭。若想使用二级缓存,还必须在mapper.xml中配置
一级缓存
我们知道,Mybatis真正运行SQL的操作都委托给了BaseExecutor.java类,每次新open一个SqlSession,都会新建一个BaseExecutor,而一级缓存实际是BaseExecutor的一个实例变量,所以一级缓存是SqlSession独有的,不同的SqlSession不能共享;
请看下面查询操作:
//代码-1 BaseExecutor的query方法
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
......
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//若localCache中没有,则从数据库中查询并放入localCache中
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
if (queryStack == 0) {
......
//若配置中配了 ,则这句话就会生效,那么localCache就会被清空。
//效果就是关闭了一级缓存,每次查询都会查到数据库中。
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
笔者觉得上述关闭一级缓存的方式并不优雅,因为总是要先把结果放入localCache中,如要关闭一级缓存,再清空。
一级缓存分成Session 和 Statement两种,Statement实际上就是没有一级缓存。
由于一级缓存是SqlSession级别的,这样就可能引发一个问题,SqlSession1修改了数据库,SqlSession2却感知不到这一变化,读到一级缓存中的脏数据。
二级缓存
二级缓存相关的类是CacheingExecutor.java,它的实例变量private final TransactionalCacheManager tcm = new TransactionalCacheManager()实际上就管理着二级缓存,其查询方法如下:
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//这里的cache就是二级缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//先查二级缓存
List list = (List) tcm.getObject(cache, key);
if (list == null) {
//二级缓存中没有,就通过BaseExecutor查询;BaseExecutor中会查询一级缓存
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // A
}
return list;
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Mybatis中的二级缓存是通过TransactionalCacheManager(tcm)维护的, 真正的二级缓存是TransactionalCache。
public class TransactionalCache implements Cache {
//存储我们查询到的数据的地方
private final Cache delegate;
private boolean clearOnCommit;
//查询的数据会先暂时放在这里,等commit()后,才能移到上面的delegate里
private final Map
缓存的初始化
从上文可知,缓存是与Executor关联在一起,使用不同的Executor执行sql,就会使用不同的缓存。下面看一下Executor的初始化:
Configuration.java
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) { //可以通过setting配置为false;默认为true;
executor = new CachingExecutor(executor); //二级缓存
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
二级缓存的初始化
二级缓存是通过解析mapper.xml中的
XMLMapperBuilder.java
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
上面的attribute实际上就是cache标签的属性,这里实例化了各种Cache,并进行了装饰,装饰后,默认情况下如下图所示;然后将该cache的引用赋值给MappedStatement实例,MappedStatement实例代表了一个sql,这样就把mapper.xml的sql语句与cache联系到了一起。当该sql查询出结果后,放入该cache中并交给TransactionalCacheManager管理。
本文参考了这里