MyBatis 提供兩級緩存架構:Session 級的一級緩存與 Namespace 級的二級緩存。本文深度解析兩者原理、配置、測試與最佳實踐。
| 特性 | 一級緩存 (Local Cache) | 二級緩存 (Second Level Cache) |
|---|---|---|
| 作用域 | SqlSession (Session 級) | Mapper/Namespace (全域) |
| 存儲結構 | PerpetualCache + HashMap | PerpetualCache + HashMap (可插件 Ehcache 等) |
| 預設狀態 | ✅ 預設開啟 | ❌ 需手動開啟 |
| 生命週期 | Session 關閉/提交時清空 | 應用存活期間,可配置淘汰策略 |
| 共享範圍 | 單一 SqlSession 內部 | 同一 Namespace 下所有 SqlSession |
| 緩存內容 | SQL 語句 + 參數 → 結果 | 查詢結果對象 (POJO) |
SqlSession 實例statement (mapper 方法)commit()、close()、clearCache()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
}
}
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<mapper namespace="me.gacl.mapping.userMapper">
<!-- 開啟二級緩存,使用預設配置 -->
<cache/>
<select id="getUser" ...>...</select>
<insert id="addUser" ...>...</insert>
<update id="updateUser" ...>...</update>
<delete id="deleteUser" ...>...</delete>
</mapper>
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 屬性與 getter/setter...
}
<cache
eviction="FIFO" <!-- 淘汰策略: LRU(預設)/FIFO/SOFT/WEAK -->
flushInterval="60000" <!-- 自動刷新間隔(毫秒),0=不自動刷新 -->
size="512" <!-- 最大緩存對象數,預設 1024 -->
readOnly="true" <!-- true=只讀(效能高),false=讀寫(需序列化) -->
/>
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():僅清空一級緩存<cache
eviction="LRU"
flushInterval="300000" <!-- 5分鐘自動刷新 -->
size="1024"
readOnly="true" <!-- 僅讀場景,效能最佳 -->
/>
一級緩存是 Session 級的自動優化,無需配置即生效;二級緩存是 Namespace 級的共享緩存,需顯式開啟並謹慎配置。理解兩者作用域、生命週期、觸發條件與序列化要求,結合業務場景選擇適當策略,才能發揮 MyBatis 緩存最大效能,避免資料不一致風險。