上一篇我們介紹了mybatis的一級緩存, 講解了一級緩存與會話的關(guān)系, 一級緩存的生命周期, 一級緩存查詢執(zhí)行的過程等, 其中也有提到二級緩存的地方, 但是都暫且略過了, 而今天這次我們就要來嗑一嗑mybatis二級緩存與一級緩存的關(guān)系 ~ 友情提示: 搭配 [http://www.itdecent.cn/p/837e6d92e747)食用更香。
NO.1 |思維發(fā)散
二級緩存是用來解決一級緩存不能跨會話共享的問題,范圍是namespace級別,可以被多個sqlSession(會話)共享, 生命周期和應(yīng)用同步。默認(rèn)關(guān)閉。
通過對一級緩存的學(xué)習(xí)我們知道, 一級緩存是默認(rèn)開啟的, 如果我們同時開啟了二級緩存, 那就勢必存在一級緩存和二級緩存都要使用的情況。這樣一來我們要思考的第一個問題產(chǎn)生了, 一級緩存和二級緩存的執(zhí)行順序是怎樣的呢?
還是先推斷一下, 二級緩存作為一個作用范圍更廣的緩存(可以跨會話), 從節(jié)省資源的角度來設(shè)計, 二級緩存肯定是要工作在一級緩存(不能跨會話)之前的。也就是只有取不到二級緩存的情況下才到一個會話中去取一級緩存。如果你的MyBatis使用了二級緩存,那么在執(zhí)行select查詢的時候,MyBatis會先從二級緩存中取數(shù)據(jù),取不到才會去走一級緩存,一級緩存中也取不到, 就會與數(shù)據(jù)庫進(jìn)行交互了。即MyBatis查詢數(shù)據(jù)的順序是:二級緩存 —> 一級緩存 —> 數(shù)據(jù)庫。
按照這種執(zhí)行順序設(shè)計來思考, 一級緩存已經(jīng)利用BaseExecutor完成了自身功能的實(shí)現(xiàn), 那么二級緩存要加在哪里進(jìn)行維護(hù),才合適呢? 實(shí)際上MyBatis這里用了一個設(shè)計模式——裝飾器模式來維護(hù)二級緩存,實(shí)現(xiàn)這個功能的類就是CachingExecutor(緩存執(zhí)行器)。
tips:裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有的類的一個包裝
具體是怎么做的呢?MyBatis讓CachingExecutor對BaseExecutor進(jìn)行了包裝。CachingExecutor中不僅要實(shí)現(xiàn)了二級緩存的功能, 同時也要持有一個基礎(chǔ)執(zhí)行器(BaseExecutor)。當(dāng)查詢請求來臨的時候,CachingExecutor會先判斷二級緩存是否有緩存結(jié)果,如果有就直接返回,如果沒有則委派交給自己持有的BaseExecutor實(shí)現(xiàn)類,比如SimpleExecutor(簡單執(zhí)行器)來執(zhí)行查詢, 這樣就順理成章的從二級緩存過渡到了一級緩存的執(zhí)行流程了。最后會把得到結(jié)果緩存起來,并且返回給用戶。
實(shí)際上是否是如此呢, 又到了喜聞樂見的放源碼時間。
NO.2 |源碼論證
(1)還是要從創(chuàng)建會話講起。
創(chuàng)建會話的過程中, 會先創(chuàng)建執(zhí)行器(具體創(chuàng)建執(zhí)行器過程參見(2)), 這個執(zhí)行器便是CachingExecutor了。然后把得到的執(zhí)行器——CachingExecutor交給DefaultSqlSession(會話)來持有。
DefaultSqlSessionFactory:
@Overridepublic SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 注意:看這里!! 創(chuàng)建Executor執(zhí)行器, 這里創(chuàng)建的是CachingExecutor
final Executor executor = configuration.newExecutor(tx, execType);
// 創(chuàng)建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
(2)創(chuàng)建執(zhí)行器部分源碼。
在newExecutor()方法中,首先還是要創(chuàng)建基礎(chǔ)執(zhí)行器(BaseExecutor)的子類, 畢竟一級緩存的邏輯還要依靠它去完成。如果開啟了緩存, 最后要創(chuàng)建一個緩存執(zhí)行器(CachingExecutor), 并把之前創(chuàng)建好的基礎(chǔ)執(zhí)行器(BaseExecutor)的子類作為CachingExecutor的有參構(gòu)造的參數(shù)傳入, 讓CachingExecutor持有BaseExecutor。并返回CachingExecutor讓DefaultSqlSession(會話)來持有。
Configuration:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 批處理執(zhí)行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 可重用執(zhí)行器
executor = new ReuseExecutor(this, transaction);
} else {
// 簡單執(zhí)行器
executor = new SimpleExecutor(this, transaction);
}
//注意: 看這里!!如果開啟緩存, 則使用緩存執(zhí)行器
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 插件執(zhí)行
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
(3)執(zhí)行查詢部分源碼
Mybatis是以sqlSession.selectList()方法, 作為查詢的入口。可以看見,在selectList() 方法中,調(diào)用了executor.query()方法來獲取數(shù)據(jù), 而sqlSession持有的Executor正是緩存執(zhí)行器(CachingExecutor)。也就是說這里調(diào)用的是CachingExecutor的query()方法。
DefaultSqlSession:
private final Configuration configuration;
private final Executor executor;
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 注意:看這里 !! 調(diào)用執(zhí)行器的查詢方法,
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在CachingExecutor的query()方法中, 如果使用了二級緩存并且二級緩存存在, 則先去二級緩存中查找數(shù)據(jù), 如果數(shù)據(jù)存在則返回數(shù)據(jù)。如果數(shù)據(jù)不存在,會直接委派給一級緩存進(jìn)行查詢。(二級緩存本身的組件結(jié)構(gòu)和實(shí)現(xiàn)邏輯沒有寫出來, 不要急, 下次會寫噠!)
CachingExecutor:
private final Executor delegate;//注意: 看這里!! 這里持有的便是基礎(chǔ)執(zhí)行器的子類
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@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) {
ensureNoOutParams(ms, boundSql);
// 注意:看這里!! 從二級緩存中查詢數(shù)據(jù)
List<E> list = (List<E>) tcm.getObject(cache, key);
// 注意:看這里!! 二級緩存中沒有數(shù)據(jù), 委托給BaseExecutor執(zhí)行
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 委托給BaseExecutor執(zhí)行
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
到這里就完成了我們整個一級緩存與二級緩存結(jié)構(gòu)關(guān)系的介紹。
NO.3 |結(jié)構(gòu)與總結(jié)
總結(jié):sqlSession 持有 CachingExecutor, CachingExecutor來完成二級緩存的功能實(shí)現(xiàn),并且持有BaseExecutor , 在二級緩存開啟并且查不到數(shù)據(jù)時(或者二級緩存本身沒有開啟), 都會委派給BaseExecutor來執(zhí)行查詢。
整體結(jié)構(gòu)圖如下:

怎么樣, 現(xiàn)在對mybatis二級緩存與一級緩存的關(guān)系是不是有了大概的了解~

這里除了編程知識分享,同時也是我成長腳步的印記, 期待與您一起學(xué)習(xí)和進(jìn)步.