2.[源碼]mybatis二級緩存源碼分析(一)----一級緩存與二級緩存的結(jié)構(gòu)關(guān)系

上一篇我們介紹了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)圖如下:


image.png

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


image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容