Mybatis源码解析之缓存机制(二):二级缓存
Mybatis源码解析之核心类分析
Mybatis源码解析之初始化分析
Mybatis源码解析之执行流程解析
Mybatis源码解析之数据库连接和连接池
Mybatis源码解析之事务管理
Mybatis源码解析之缓存机制(一):一级缓存
为了节约每次连接数据库查询数据库的资源消耗,并提高查询效率,Mybatis提供了双重缓存机制,包括HashMap结构的一级缓存和Cache结构的二级缓存。
本文主要对二级缓存进行解析。
一、缓存机制
二、二级缓存的配置及其解析
二级缓存又被称作全局缓存,作用域是整个application。
1. 全局cacheEnabled参数的配置
Mybatis通过setting节点的cacheEnabled参数对Mybatis是否启用二级缓存进行配置。
<!--cacheEnabled用于全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true -->
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
2. 全局cacheEnabled参数的解析
同样的,Mybatis通过XMLConfigBuilder#parse()方法对配置文件进行解析,具体对于cacheEnabled参数的解析在XMLConfigBuilder#settingsElement(Xnode)方法的configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
语句,具体对于Configuration的cacheEnabled属性。
前面的博客里,我们已经知道Mybatis通过Configuration#newExecutor(Transaction, ExecutorType)得到每次会话SqlSession中使用的Executor。
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;
}
可以看到,在cacheEnabled属性为true时,SqlSession使用的是CachingExecutor。
3. mapper cache的配置
二级缓存是全局有效的,但其粒度时mapper级别的,每个mapper都可以有一个cache,且互相独立。具体cache的配置可以通过mapper中的cache节点或者cache-ref节点进行配置。
(1)cache节点
<cache type=“PERPETUAL” eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
(2)cache-ref节点
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
cache-ref节点的通过namespace指定和对应的mapper共用一个缓存。
4. mapper cache的配置解析
按照之前博客的分析,mapper文件的解析在XMLMapperBuilder#parse()方法进行,具体cache节点的解析在cacheElement(XNode)方法进行,cache-ref节点的解析在cacheRefElement(XNode)方法进行。
(1)XMLMapperBuilder#cacheElement(XNode)
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);
}
}
MappedBuilderAssistant的useNewCache方法
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
(2)XMLMapperBuilder#cacheRefElement(XNode)
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
Configuration类中有一个Map结构的caches属性,其key值就是cache的id,也就是namespace,XMLMapperBuilder#cacheRefElement(XNode)就是根据namespace属性找到对应的cache,并且将当前的namespace和cache-ref指向的namespace加入到cacheRefMap中。
5. select useCache和flush参数的配置和解析
我们还可以在select语句中通过useCache属性配置该查询是否使用二级缓存,默认为true。
另外,flushCache的参数如果设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认为false。
useCache属性只适用于select语句,flushCache则适用于包括select、update、delete、insert在内的所有动态sql。
具体的解析代码在MapperBuilderAssistant的addMappedStatement方法中。
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
三、二级缓存的处理
1. Cache
Mybatis中,二级缓存对应的接口是Cache,同时还提供了众多的实现类,但是除了PerpetualCache外,其他都是Cache的装饰器。PerpetualCache也是mybatis中默认使用的缓存类型。
Perpetual的将缓存以HashMap格式进行存放,CachKey作为map的key值,查询结果作为map的value值。
2. CachingExecutor
CachingExecutor是Executor的装饰器,增加了二级缓存的处理逻辑。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
//mapper配置了缓存
if (cache != null) {
//flush为true时清理缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
//CALLABLE时的参数校验
ensureNoOutParams(ms, parameterObject, 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;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}