MyBatis 緩存 - 下:二級緩存

通過上篇文章我們已經知道,MyBatis 一級緩存的最大共享範圍爲 SqlSession,即一次會話中,而且有可能會因爲緩存沒更新而導致髒讀問題。如果需要在多個 SqlSession 中共享緩存,那麼就需要開啓二級緩存。

SqlSession 的創建會藉助 SqlSessionFactory,而 DefaultSqlSession 可由 DefaultSqlSessionFactory#openSession
獲得,Executor 通過 Configuration#newExecutor 方法獲得,該方法會檢查 cacheEnabled 配置,開啓就意味着啓用二級緩存:
image.png

配置

MyBatis 配置文件通過如下配置開啓:

<setting name="cacheEnabled" value="true"/>

一個 mapper 文件有唯一的 namespace (<mapper namespace="***">),在 mapper 文件中可通過 <cache/> 標籤聲明該 namespace 使用二級緩存。<cache-ref/> 可配置引用其他的命名空間,那麼當前命名空間將與引用的命名空間使用同一個緩存(對於同一命名空間下的多表查詢可藉助該標籤避免髒讀問題)。<cache/> 標籤有如下可選屬性:

  • type:cache 類型,默認是 PerpetualCache
  • eviction: 回收的策略,比如 FIFO,LRU。
  • flushInterval: 配置一定時間自動刷新緩存,單位是毫秒。
  • size: 最多緩存對象的個數。
  • readOnly: 是否只讀,若配置可讀寫,則需要對應的實體類能夠序列化。
  • blocking: 若緩存中找不到對應的key,是否一直阻塞,直到有對應的數據進入緩存。

裝飾器模式以及 TransactionalCacheManager

CachingExecutor 使用了裝飾器模式,delegate 即爲被裝飾的 Executor 對象,tcm 爲 TransactionalCacheManager 類型,tcm 管理了以 namespace 爲單位的緩存。
從
先看 putObject 方法,內部調用了 getTransactionalCache 方法,該方法從 transactionalCaches 中取到了具體的 TransactionalCache,這裏以 namespace 爲單位的緩存作爲 key,將這個 Cache 進行再包裝的 TransactionalCache 作爲值,TransactionalCache 處理了事務相關的一些特性。

緩存更新和獲取

緩存的獲取與一級緩存無異,都封裝在 Executor#query 中:
image.png
區別是二級緩存通過 TransactionalCacheManager 來操作緩存。

在默認的設置中 SELECT(Executor#query) 語句不會刷新緩存,insert/update/delte (Executor#update)會刷新緩存。

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

調用 SqlSession#commit 二級緩存纔會生效

image.png

已經知道在 Executor#query 方法中如果緩存沒命中,那麼從數據庫中查詢後會將數據新增到緩存中,但實際上此時還沒有將數據放入緩存( HashMap),那 tcm.putObject 語句把數據放到了哪裏呢 ?

tcm.putObject 會根據 Cache (作爲 key)從 TransactionalCacheManager#transactionalCaches 中得到包裝後的 TransactionalCache,再調用其 TransactionalCache#putObject 方法:
image.png
通過變量名就能知道 entriesToAddOnCommit 在調用 commit 時纔會真正放入緩存的 HashMap 中,事實也是如此:
image.png

entriesMissedInCache 用於統計命中率。

參考文章:聊聊 MyBatis 緩存機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章