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

MyBatis 緩存機制深度解析:一級緩存與二級緩存實戰

     編輯:Java 相關知識

MyBatis 緩存機制深度解析:一級緩存與二級緩存完整指南

MyBatis 提供兩級緩存架構:Session 級的一級緩存與 Namespace 級的二級緩存。本文深度解析兩者原理、配置、測試與最佳實踐。

緩存架構總覽

特性一級緩存 (Local Cache)二級緩存 (Second Level Cache)
作用域SqlSession (Session 級)Mapper/Namespace (全域)
存儲結構PerpetualCache + HashMapPerpetualCache + HashMap (可插件 Ehcache 等)
預設狀態✅ 預設開啟❌ 需手動開啟
生命週期Session 關閉/提交時清空應用存活期間,可配置淘汰策略
共享範圍單一 SqlSession 內部同一 Namespace 下所有 SqlSession
緩存內容SQL 語句 + 參數 → 結果查詢結果對象 (POJO)

一級緩存詳細機制

觸發條件

  • 同一 SqlSession 實例
  • 相同的 statement (mapper 方法)
  • 相同的參數值
  • 未執行過 commit()、close()、clearCache()
  • 未執行過增刪改操作 (CUD 會清空緩存)

測試代碼 - TestOneLevelCache.java

package me.gacl.test;

import me.gacl.domain.User;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestOneLevelCache {
    @Test
    public void testCache() {
        SqlSession session = MyBatisUtil.getSqlSession();
        String statement = "me.gacl.mapping.userMapper.getUser";
        
        // 第一次查詢 - 發送 SQL
        User user1 = session.selectOne(statement, 1);
        System.out.println("第一次: " + user1);
        
        // 第二次查詢 - 命中一級緩存,不發送 SQL
        User user2 = session.selectOne(statement, 1);
        System.out.println("第二次: " + user2);
        System.out.println("是否同一對象: " + (user1 == user2)); // true
        
        session.close();
        
        // 新 Session - 一級緩存失效
        session = MyBatisUtil.getSqlSession();
        User user3 = session.selectOne(statement, 1);
        System.out.println("新 Session: " + user3);
        
        // 手動清空緩存
        // session.clearCache();
        
        // 執行更新 - 觸發緩存清空
        session.update("me.gacl.mapping.userMapper.updateUser", 
            new User(1, "updated", 25));
        User user4 = session.selectOne(statement, 1);
        System.out.println("更新後查詢: " + user4); // 重新發送 SQL
    }
}

二級緩存詳細機制

啟用步驟

1. 全域配置 - conf.xml
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
2. Mapper 級開啟 - userMapper.xml
<mapper namespace="me.gacl.mapping.userMapper">
    <!-- 開啟二級緩存,使用預設配置 -->
    <cache/>
    
    <select id="getUser" ...>...</select>
    <insert id="addUser" ...>...</insert>
    <update id="updateUser" ...>...</update>
    <delete id="deleteUser" ...>...</delete>
</mapper>
3. 實體類實現 Serializable
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // 屬性與 getter/setter...
}

進階配置 - cache 標籤屬性

<cache 
    eviction="FIFO"           <!-- 淘汰策略: LRU(預設)/FIFO/SOFT/WEAK -->
    flushInterval="60000"     <!-- 自動刷新間隔(毫秒),0=不自動刷新 -->
    size="512"                <!-- 最大緩存對象數,預設 1024 -->
    readOnly="true"           <!-- true=只讀(效能高),false=讀寫(需序列化) -->
/>

淘汰策略說明

  • LRU (Least Recently Used):移除最久未訪問對象,預設策略
  • FIFO (First In First Out):按進入順序移除
  • SOFT:基於軟引用,JVM 記憶體不足時回收
  • WEAK:基於弱引用,GC 時回收

測試代碼 - TestTwoLevelCache.java

package me.gacl.test;

import me.gacl.domain.User;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

public class TestTwoLevelCache {
    @Test
    public void testCache() {
        String statement = "me.gacl.mapping.userMapper.getUser";
        SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
        
        // 兩個不同的 SqlSession
        SqlSession session1 = factory.openSession();
        SqlSession session2 = factory.openSession();
        
        // Session1 第一次查詢 - 發送 SQL,結果存入二級緩存
        User user1 = session1.selectOne(statement, 1);
        session1.commit(); // 必須 commit 才會寫入二級緩存!
        System.out.println("Session1: " + user1);
        
        // Session2 查詢相同條件 - 命中二級緩存,不發送 SQL
        User user2 = session2.selectOne(statement, 1);
        // session2.commit(); // 查詢可不 commit
        System.out.println("Session2: " + user2);
        System.out.println("是否同一對象: " + (user1 == user2)); // false (反序列化新對象)
    }
}

緩存更新機制

自動清空觸發條件

當 Namespace 範圍內執行任意 增、刪、改 操作 (insert/update/delete) 時,MyBatis 自動清空該 Namespace 下的所有二級緩存。

手動控制

  • flushCache="true":在 select 標籤上強制刷新緩存
  • useCache="false":在 select 標籤上禁用緩存 (不讀不寫)
  • sqlSession.clearCache():僅清空一級緩存

最佳實踐與避坑指南

✅ 適合啟用二級緩存的場景

  • 讀多寫少的參考資料 (字典表、配置表、產品分類)
  • 容許短暫髒讀 (幾秒~幾分鐘延遲可接受)
  • 查詢條件固定、結果集較小
  • 單機部署或配合分散式緩存 (Redis) 使用

❌ 不適合啟用二級緩存的場景

  • 高頻寫入表 (訂單、日誌、用戶狀態)
  • 強一致性要求 (金額、庫存、權限)
  • 分散式部署未共享緩存 (各節點緩存不一致)
  • 大結果集查詢 (佔用過多堆記憶體)

⚠️ 常見坑點

  1. 忘記實現 Serializable:readOnly=false 時會拋出 NotSerializableException
  2. 未 commit 以為生效:二級緩存寫入發生在 commit() 時刻
  3. 分散式環境緩存不一致:需整合 Redis/Ehcache 實現分散式二級緩存
  4. 緩存穿透/雪崩:熱點資料過期瞬間大量請求打到 DB,需互斥鎖或預熱
  5. 序列化效能開銷:readOnly=false 時每次獲取都需反序列化,效能損耗大

生產環境建議配置

<cache 
    eviction="LRU"
    flushInterval="300000"    <!-- 5分鐘自動刷新 -->
    size="1024"
    readOnly="true"           <!-- 僅讀場景,效能最佳 -->
/>

總結

一級緩存是 Session 級的自動優化,無需配置即生效;二級緩存是 Namespace 級的共享緩存,需顯式開啟並謹慎配置。理解兩者作用域、生命週期、觸發條件與序列化要求,結合業務場景選擇適當策略,才能發揮 MyBatis 緩存最大效能,避免資料不一致風險。

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