MyBatis 提供兩級緩存機制應對高頻查詢性能瓶頸:一級緩存(SqlSession 級)默認開啟,二級緩存(Mapper 級)需顯式配置。本文從源碼層面完整追蹤查詢執行流程、緩存鍵生成策略、緩存實例化裝飾鏈,揭示核心設計原理。
// DefaultSqlSession.selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
// BaseExecutor.query() - 構建 CacheKey
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// CachingExecutor.query() - 二級緩存攔截點(若開啟)
// BaseExecutor.query() - 一級緩存核心邏輯
public <E> List<E> query(...) {
if (queryStack == 0 && ms.isFlushCacheRequired()) clearLocalCache();
List<E> list = (resultHandler == null) ? (List<E>) localCache.getObject(key) : null;
if (list != null) handleLocallyCachedOutputParameters(...);
else list = queryFromDatabase(...);
return list;
}
localCache 定義在 Executor(默認 SimpleExecutor)中,而非 SqlSession;SqlSession 關閉時 Executor 隨之回收SqlSession 存在即擁有 PerpetualCache(底層 HashMap,無容量控制)BaseExecutor.update() 首行調用 clearLocalCache(),防止髒讀;flushCache="true" 可強制查詢前清空CacheKey 封裝 updateList,equals() 逐項比對:
namespace + select idoffset、limit(分頁參數)boundSql.getSql()(含參數佔位符)三者完全一致且未配置 flushCache="true",直接命中一級緩存。
CachingExecutor.query() 先檢查二級緩存(tcm.getObject()),未命中再委託 delegate(BaseExecutor)查詢一級緩存/數據庫。結論:開啟二級緩存時,二級緩存讀取優先級高於一級緩存。
Cache cache = ms.getCache(); // 從 MappedStatement 獲取 Cache 實例
if (cache != null) {
flushCacheIfRequired(ms); // 根據 flushCache 屬性決定是否清理
if (ms.isUseCache() && resultHandler == null) {
List<E> list = (List<E>) tcm.getObject(cache, key); // 事務緩存獲取
if (list == null) {
list = delegate.query(...); // 回退一級緩存/DB
tcm.putObject(cache, key, list); // 寫入二級緩存
}
return list;
}
}
return delegate.query(...);
tcm = TransactionalCacheManager,裝飾器模式管理事務緩存CacheKey 與一級緩存共用,判定條件一致MappedStatement 存儲在 Configuration.mappedStatements,應用啟動時構建,應用結束才銷毀size:最大條目數eviction:淘汰算法(默認 LRU)flushInterval:定期清理間隔readOnly:只讀模式避免序列化開銷type="PERPETUAL" (默認) → PerpetualCache eviction="LRU" (默認) → LruCache flushInterval, size, readWrite, blocking, properties
newBaseCacheInstance(implementation, id) → PerpetualCacheLruCache 包裝實現 LRU 淘汰ScheduledCache (flushInterval) → 定時清理 SerializedCache (readWrite=true) → 序列化支援(要求值實現 Serializable) LoggingCache → 命中率日誌 SynchronizedCache → 線程安全同步 BlockingCache (blocking=true) → 同鍵阻塞等待
PerpetualCache + 全套裝飾Cache 接口,僅裝飾 LoggingCacheLoggingCache(自帶淘汰/過期機制)MyBatis 緩存設計精妙體現於:一級緩存輕量無感、二級緩存可配置擴展、裝飾器模式靈活組合功能、CacheKey 統一語義。生產環境建議:默認開啟一級緩存、熱點數據開啟二級緩存並配置淘汰策略、分佈式場景接入 Redis 等中心化緩存替代本地二級緩存。