通過上篇文章我們已經知道,MyBatis 一級緩存的最大共享範圍爲 SqlSession,即一次會話中,而且有可能會因爲緩存沒更新而導致髒讀問題。如果需要在多個 SqlSession 中共享緩存,那麼就需要開啓二級緩存。
SqlSession 的創建會藉助 SqlSessionFactory,而 DefaultSqlSession
可由 DefaultSqlSessionFactory#openSession
獲得,Executor 通過 Configuration#newExecutor
方法獲得,該方法會檢查 cacheEnabled
配置,開啓就意味着啓用二級緩存:
配置
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
中:
區別是二級緩存通過 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
二級緩存纔會生效
已經知道在 Executor#query
方法中如果緩存沒命中,那麼從數據庫中查詢後會將數據新增到緩存中,但實際上此時還沒有將數據放入緩存( HashMap
),那 tcm.putObject
語句把數據放到了哪裏呢 ?
tcm.putObject
會根據 Cache (作爲 key)從 TransactionalCacheManager#transactionalCaches
中得到包裝後的 TransactionalCache
,再調用其 TransactionalCache#putObject
方法:
通過變量名就能知道 entriesToAddOnCommit
在調用 commit
時纔會真正放入緩存的 HashMap 中,事實也是如此:
entriesMissedInCache
用於統計命中率。
參考文章:聊聊 MyBatis 緩存機制