注: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; }