注:Mybatis的版本是3.5.0。java
上一篇分析了一级缓存,这篇来分析二级缓存。git
如下的内容,跳过了不少细节,能够去看这篇博客。github
一级缓存只能在同一个SqlSession里面共享,而二级缓存则能够在多个SqlSession里面共享。sql
开启二级缓存,那么使用的是CachingExecutor和SimpleExecutor(不修改默认设置的状况下),以下List-1所示,SimpleExecutor是封装在CachingExecutor中的。数据库
List-1 apache
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) { //将SimpleExecutor封装起来 executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
CachingExecutor使用了委托模式,其属性"Executor delegate",缓存
List-2安全
package org.apache.ibatis.executor; ...... /** * @author Clinton Begin * @author Eduardo Macarron */ public class CachingExecutor implements Executor { private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } ......
以下图1所示,CachingExecutor中使用的Cache是SynchronizedCache,它里面还封装了不少Cache,最终数据是存储在PerpetualCache中,是个HashMap。session
图1 CachingExecutor中使用的Cache是SynchronizedCachemybatis
因为二级缓存Cache封装在SynchronizedCache中,因此对二级缓存的操做是线程安全的,SynchronizedCache的几乎每一个方法上都加了Sychronized,这在实现线程安全的同时,也在必定程度上成了瓶颈,特别是对二级缓存操做的线程数量不少时。
在List-2中的属性tcm,这个是理解二级缓存的一个关键点。
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
session1提交后,session2才能看到session1缓存的结果。
在配置了二级缓存以后,select时,是二级缓存->一级缓存->数据库,以下是CachingExecutor中的query:
List-3
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @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) {//1 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key);//2 if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //3 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
在List-3中
List-4
<select id="findByUsername" resultType="Person" parameterType="Person" useCache="false">
MappedStatement中的useCache的值设置在MapperBuilderAssistant中,以下List-5的1处,若是是select标签,在没有显示设置useCache的状况下是true。
List-5
public MappedStatement addMappedStatement( ... id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 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))//1 .cache(currentCache); } private <T> T valueOrDefault(T value, T defaultValue) { return value == null ? defaultValue : value; }