MyBatis之緩存機制詳解
- 一、MyBatis緩存的基本概念
- 1.1 緩存的核心價值
- 1.2 MyBatis的兩級緩存體系
- 二、一級緩存(SqlSession級別緩存)
- 2.1 工作原理
- 2.2 實戰案例:一級緩存演示
- 2.2.1 基礎用法(默認開啟)
- 2.2.2 一級緩存失效場景
- 2.3 一級緩存的特點與適用場景
- 三、二級緩存(Mapper級別緩存)
- 3.1 工作原理
- 3.2 二級緩存的開啟與配置
- 3.2.1 全局配置(可選)
- 3.2.2 Mapper接口開啟緩存
- 3.2.3 實體類序列化(必須)
- 3.3 實戰案例:二級緩存演示
- 3.4 二級緩存的特點與適用場景
- 四、二級緩存的高級配置
- 4.1 禁用特定查詢的二級緩存
- 4.2 強制刷新二級緩存
- 4.3 整合第三方緩存(如Redis)
- 4.3.1 引入依賴(Redis+MyBatis-Redis)
- 4.3.2 配置Redis緩存
- 五、緩存使用的常見問題與避坑指南
- 5.1 一級緩存導致的臟讀問題
- 5.2 二級緩存的序列化問題
- 5.3 緩存與事務的一致性問題
- 5.4 過度使用緩存導致內存溢出
緩存是提升數據庫查詢性能的關鍵技術,MyBatis內置了兩級緩存機制,能有效減少重復查詢的數據庫交互,降低數據庫壓力。
一、MyBatis緩存的基本概念
1.1 緩存的核心價值
數據庫查詢是應用性能的常見瓶頸(磁盤IO比內存IO慢10^6倍以上),緩存通過將頻繁查詢的結果存儲在內存中,避免重復訪問數據庫,從而:
- 減少數據庫連接和SQL執行次數;
- 降低數據庫服務器壓力;
- 提升應用響應速度(從內存讀取比數據庫查詢快100倍以上)。
1.2 MyBatis的兩級緩存體系
MyBatis提供兩級緩存,工作流程如下:
- 一級緩存(SqlSession級別):默認開啟,緩存當前會話(SqlSession)的查詢結果;
- 二級緩存(Mapper級別):需手動開啟,緩存Mapper接口的查詢結果,可被多個SqlSession共享。
查詢數據時,MyBatis的緩存查詢順序:
二級緩存 → 一級緩存 → 數據庫
即先查二級緩存,若未命中則查一級緩存,仍未命中才查詢數據庫。
二、一級緩存(SqlSession級別緩存)
一級緩存是MyBatis的默認緩存,綁定到SqlSession
(會話),生命周期與SqlSession
一致。
2.1 工作原理
- 緩存范圍:每個
SqlSession
擁有獨立的一級緩存,不同SqlSession
的緩存互不影響; - 緩存時機:
SqlSession
執行select
查詢后,會將結果存入一級緩存; - 命中條件:相同的
Mapper方法
+相同的參數
+相同的SQL
; - 失效場景:
SqlSession
執行insert
/update
/delete
(會清空當前SqlSession
的一級緩存)、SqlSession
關閉或提交。
2.2 實戰案例:一級緩存演示
2.2.1 基礎用法(默認開啟)
// 獲取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 第一次查詢(未命中緩存,查詢數據庫)
User user1 = userMapper.selectById(1); // 第二次查詢(相同SqlSession+相同參數,命中一級緩存,不查數據庫)
User user2 = userMapper.selectById(1); System.out.println(user1 == user2); // true(同一對象,從緩存獲取)sqlSession.close(); // 關閉會話,一級緩存失效
2.2.2 一級緩存失效場景
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user1 = userMapper.selectById(1); // 執行更新操作(insert/update/delete),清空一級緩存
userMapper.updateAge(1, 25);
sqlSession.commit(); // 提交事務(觸發緩存清空)// 再次查詢(緩存已清空,重新查詢數據庫)
User user2 = userMapper.selectById(1);
System.out.println(user1 == user2); // false(不同對象,從數據庫獲取)
2.3 一級緩存的特點與適用場景
特點 | 說明 |
---|---|
默認開啟 | 無需配置,開箱即用 |
會話隔離 | 不同SqlSession的緩存獨立,避免數據沖突 |
自動管理 | 增刪改自動清空緩存,保證數據一致性 |
適用場景:
- 單會話內的頻繁查詢(如同一請求中多次查詢相同用戶信息);
- 讀多寫少的場景(避免頻繁查詢數據庫)。
三、二級緩存(Mapper級別緩存)
二級緩存是跨SqlSession
的全局緩存,綁定到Mapper接口
(同一Mapper的所有方法共享),需手動開啟。
3.1 工作原理
- 緩存范圍:同一Mapper接口的所有
SqlSession
共享二級緩存; - 緩存時機:
SqlSession
關閉(close()
)或提交(commit()
)后,一級緩存的結果會寫入二級緩存; - 命中條件:相同的
Mapper接口
+相同的方法
+相同的參數
; - 失效場景:Mapper接口執行
insert
/update
/delete
(會清空當前Mapper的二級緩存)。
3.2 二級緩存的開啟與配置
3.2.1 全局配置(可選)
在mybatis-config.xml
中開啟二級緩存(默認已開啟,可省略):
<settings><setting name="cacheEnabled" value="true"/> <!-- 全局二級緩存開關 -->
</settings>
3.2.2 Mapper接口開啟緩存
在需要使用二級緩存的Mapper XML
中添加<cache>
標簽:
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper"><!-- 開啟二級緩存 --><cache eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用) -->flushInterval="60000" <!-- 自動刷新間隔(毫秒,60秒) -->size="1024" <!-- 最大緩存條目 -->readOnly="false"/> <!-- 是否只讀(false:緩存對象副本) --><!-- 查詢語句(默認使用二級緩存) --><select id="selectById" resultType="User">SELECT id, username, age FROM user WHERE id = #{id}</select>
</mapper>
<cache>
標簽屬性說明:
eviction
:緩存淘汰策略(LRU
:移除最近最少使用;FIFO
:先進先出);flushInterval
:緩存自動刷新時間(毫秒,0表示不自動刷新);size
:最大緩存數量(過多會占用內存);readOnly
:true
(返回緩存對象本身,性能好但線程不安全);false
(返回副本,安全但性能略低)。
3.2.3 實體類序列化(必須)
二級緩存可能將對象寫入磁盤(如使用第三方緩存),因此實體類需實現Serializable
接口:
// 實現Serializable接口
public class User implements Serializable {private Integer id;private String username;private Integer age;// getter/setter
}
3.3 實戰案例:二級緩存演示
// 第一個SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById(1);
sqlSession1.close(); // 關閉會話,將一級緩存寫入二級緩存// 第二個SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 命中二級緩存(無需查詢數據庫)
User user2 = userMapper2.selectById(1);
sqlSession2.close();System.out.println(user1 == user2); // false(二級緩存返回副本,readOnly=false時)
System.out.println(user1.getId().equals(user2.getId())); // true(數據一致)
3.4 二級緩存的特點與適用場景
特點 | 說明 |
---|---|
手動開啟 | 需要在Mapper中配置<cache> 標簽 |
跨會話共享 | 同一Mapper的所有SqlSession可共享緩存 |
支持序列化 | 可配置第三方緩存(如Redis)持久化緩存 |
適用場景:
- 多會話共享的高頻查詢(如商品分類、字典表等不常變化的數據);
- 讀多寫少的場景(避免頻繁更新導致緩存失效)。
四、二級緩存的高級配置
4.1 禁用特定查詢的二級緩存
若某查詢不需要使用二級緩存(如實時性要求高的數據),可通過useCache="false"
禁用:
<select id="selectLatestOrder" resultType="Order" useCache="false">SELECT * FROM `order` ORDER BY create_time DESC LIMIT 1
</select>
4.2 強制刷新二級緩存
若需在查詢時強制刷新緩存(忽略現有緩存,重新查詢數據庫并更新緩存),可使用flushCache="true"
:
<select id="selectUserWithForceRefresh" resultType="User" flushCache="true">SELECT * FROM user WHERE id = #{id}
</select>
4.3 整合第三方緩存(如Redis)
MyBatis的默認二級緩存是內存緩存(重啟后失效),生產環境通常整合Redis等分布式緩存,實現緩存持久化和分布式共享。
4.3.1 引入依賴(Redis+MyBatis-Redis)
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.7.0</version>
</dependency>
4.3.2 配置Redis緩存
<!-- UserMapper.xml:指定緩存實現為Redis -->
<cache type="org.mybatis.caches.redis.RedisCache"><property name="host" value="localhost"/> <!-- Redis主機 --><property name="port" value="6379"/> <!-- Redis端口 --><property name="timeout" value="30000"/> <!-- 超時時間 --><property name="expiration" value="3600000"/> <!-- 緩存過期時間(毫秒) -->
</cache>
優勢:
- 緩存持久化(應用重啟后緩存不丟失);
- 分布式共享(多實例應用共享緩存);
- 支持緩存過期策略(自動清理過期數據)。
五、緩存使用的常見問題與避坑指南
5.1 一級緩存導致的臟讀問題
問題:多SqlSession場景下,一級緩存可能讀取到舊數據。
// SqlSession1查詢數據并緩存
SqlSession sqlSession1 = sqlSessionFactory.openSession();
User user1 = sqlSession1.getMapper(UserMapper.class).selectById(1);// SqlSession2更新數據并提交
SqlSession sqlSession2 = sqlSessionFactory.openSession();
sqlSession2.getMapper(UserMapper.class).updateAge(1, 26);
sqlSession2.commit();
sqlSession2.close();// SqlSession1再次查詢(一級緩存未更新,讀取到舊數據)
User user2 = sqlSession1.getMapper(UserMapper.class).selectById(1);
System.out.println(user2.getAge()); // 25(舊值,而非更新后的26)
解決方案:
- 避免長生命周期的SqlSession(如Web應用中,一個請求對應一個SqlSession);
- 對實時性要求高的查詢,禁用一級緩存(通過
flushCache="true"
); - 使用二級緩存(跨SqlSession共享,更新后會同步)。
5.2 二級緩存的序列化問題
錯誤:實體類未實現Serializable
接口,二級緩存報錯NotSerializableException
。
解決方案:
- 確保所有存入二級緩存的實體類實現
Serializable
接口; - 若使用第三方緩存(如Redis),需保證對象可被序列化(如避免循環引用)。
5.3 緩存與事務的一致性問題
問題:事務未提交時,其他SqlSession可能讀取到未提交的緩存數據(臟讀)。
原因:
- 一級緩存在事務內生效,未提交的更新不會刷新其他SqlSession的緩存;
- 二級緩存僅在事務提交后才更新,避免此問題。
解決方案:
- 優先使用二級緩存(事務提交后才寫入,保證數據一致性);
- 關鍵業務(如支付)避免依賴緩存,直接查詢數據庫。
5.4 過度使用緩存導致內存溢出
問題:緩存大量數據(如全表查詢結果),導致JVM內存溢出(OOM)。
解決方案:
- 限制緩存大小(
size
屬性,如size="1024"
); - 設置緩存過期時間(
flushInterval
); - 避免緩存大集合(如分頁查詢,只緩存當前頁數據);
- 使用分布式緩存(如Redis),利用外部內存存儲緩存。
總結:緩存機制的最佳實踐
MyBatis的兩級緩存各有適用場景,合理使用能顯著提升性能,核心實踐原則
:
- 一級緩存:
- 無需額外配置,充分利用其默認特性;
- 注意SqlSession的生命周期(一個請求一個SqlSession),避免臟讀;
- 增刪改操作后,一級緩存會自動清空,無需手動處理。
- 二級緩存:
- 僅對“讀多寫少、實時性要求低”的數據開啟(如字典表、商品分類);
- 必須實現實體類序列化,避免緩存失敗;
- 生產環境推薦整合Redis等分布式緩存,支持持久化和分布式共享;
- 對實時性要求高的查詢(如訂單狀態),禁用二級緩存。
- 通用原則:
- 緩存粒度越小越好(優先緩存單條數據,而非全表);
- 避免緩存大對象和頻繁變化的數據;
- 結合監控工具(如MyBatis Log)分析緩存命中率,優化緩存策略。
若這篇內容幫到你,動動手指支持下!關注不迷路,干貨持續輸出!
ヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノ