首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深入解析 MyBatis 框架的緩存機制與優化實踐

     編輯:Java 相關知識

MyBatis 兩級緩存架構詳解

MyBatis 將資料緩存設計為兩級結構:一級緩存(Local Cache)與二級緩存(Application Cache)。兩者在生命週期、作用範圍及配置方式上存在顯著差異。

一級緩存:Session 級別的本地緩存

  • 生命週期:與 SqlSession 同步,一次資料庫會話內有效。
  • 存儲位置:位於 Executor 物件內部的 Cache 物件中。
  • 特點:MyBatis 內建功能,預設自動開啟,用戶無法直接配置(可透過插件擴展)。
  • 工作機制:同一 SqlSession 內重複查詢相同 SQL 且參數相同時,直接返回緩存結果,避免重複查詢資料庫。

二級緩存:Application 級別的全域緩存

  • 生命週期:與應用程式同步,跨 SqlSession 共享。
  • 啟用條件:需在配置中設定 cacheEnabled=true。
  • 核心原理:MyBatis 使用裝飾者模式,為 Executor 包裝一層 CachingExecutor。查詢時優先檢查二級緩存,命中則直接返回;未命中則委託真實 Executor 查詢後存入緩存。
  • 擴展性:支援自定義緩存(實作 org.apache.ibatis.cache.Cache 接口)及第三方緩存(如 Memcached、Redis)。

常見緩存問題與根因分析

典型症狀

  1. 開啟緩存後,分頁查詢無論請求哪一頁,皆返回第一頁資料。
  2. 使用 SQL 自動生成插件時,傳入參數失效,總是返回第一組參數的查詢結果。

根本原因

MyBatis 生成緩存 Key 的時機在 Executor 階段,而分頁插件與 SQL 生成插件在 StatementHandler 階段執行(晚於 Executor)。這些插件通過重寫 SQL 實現功能,但緩存 Key 卻基於原始 SQL 與原始參數生成,導致實際執行的 SQL 與緩存 Key 不匹配,產生錯誤命中。

解決方案:攔截器重寫緩存 Key 生成邏輯

核心思路:攔截 Executor.query() 方法,在生成 CacheKey 時融合分頁資訊與重寫後的 SQL。

攔截器簽名

@Intercepts({
  @Signature(type = Executor.class, method = "query", args = {
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
  })
})
public class CacheInterceptor implements Interceptor { ... }

核心實現邏輯

  1. 穿透代理鏈:遞歸獲取最底層的真實 Executor 目標物件。
  2. 構建增強 CacheKey:
    • 加入分頁參數(offset、limit)
    • 處理動態 SQL 生成場景(boundSql.getSql() 為空時重新構建 SQL)
    • 融合實際參數值
    • 識別分頁查詢 ID(透過配置 pageSqlId 正則匹配),額外加入當前頁碼與頁大小
  3. 委託執行:使用新 CacheKey 呼叫真實 Executor.query()。

關鍵代碼片段

private CacheKey createCacheKey(MappedStatement ms, Object parameterObject, 
    RowBounds rowBounds, BoundSql boundSql) {
  CacheKey cacheKey = new CacheKey();
  cacheKey.update(ms.getId());
  cacheKey.update(rowBounds.getOffset());
  cacheKey.update(rowBounds.getLimit());
  
  // 處理動態 SQL 生成場景
  if (boundSql.getSql() == null || boundSql.getSql().isEmpty()) {
    String newSql = SqlBuilder.buildSelectSql(parameterObject);
    cacheKey.update(newSql);
    // 更新參數映射...
  } else {
    cacheKey.update(boundSql.getSql());
  }
  
  // 融合參數值
  // ...
  
  // 處理分頁參數
  if (ms.getId().matches(pageSqlId) && metaObject.hasGetter("page")) {
    PageParameter page = (PageParameter) metaObject.getValue("page");
    cacheKey.update(page.getCurrentPage());
    cacheKey.update(page.getPageSize());
  }
  return cacheKey;
}

插件封裝策略

public Object plugin(Object target) {
  // 僅包裝 CachingExecutor,避免重複代理
  return (target instanceof CachingExecutor) ? Plugin.wrap(target, this) : target;
}

最佳實踐建議

  • 合理配置緩存範圍:僅對頻繁讀取、較少變更的資料啟用二級緩存。
  • 緩存失效策略:配合 <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/> 微調淘汰算法、刷新間隔與容量。
  • 避免緩存穿透:對不存在的資料快取空結果,防止大量無效查詢直擊資料庫。
  • 監控指標:定期檢視緩存命中率、記憶體佔用,及時調整配置。

總結

MyBatis 的兩級緩存機制為查詢效能提供了強有力支撐,但預設實現與分頁、動態 SQL 等插件存在時序衝突。透過自定義攔截器重寫 CacheKey 生成邏輯,融合執行時上下文(分頁參數、重寫後 SQL、實際參數值),可徹底解決緩存錯誤命中問題。實務中應結合業務特性靈活配置緩存策略,並建立監控體系確保緩存層穩定高效運作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved