Mybatis中的緩存機制解析
核心概覽
- 一級緩存:默認開啟,作用范圍在 同一個 SqlSession 內(nèi)。
- 二級緩存:需要手動配置開啟,作用范圍在 同一個 Mapper 命名空間(即同一個 Mapper 接口)內(nèi),可以被多個 SqlSession 共享。
一級緩存
1. 作用域
- SqlSession 級別:當(dāng)同一個 SqlSession 執(zhí)行相同的 SQL 查詢時,MyBatis 會優(yōu)先從緩存中獲取數(shù)據(jù),而不是直接查詢數(shù)據(jù)庫。
- 它是 默認開啟 的,無法關(guān)閉,但可以配置其作用范圍(
SESSION或STATEMENT)。
2. 工作機制
- 第一次執(zhí)行查詢后,查詢結(jié)果會被存儲到 SqlSession 關(guān)聯(lián)的一級緩存中。
- 在同一個 SqlSession 中,再次執(zhí)行 完全相同的 SQL 查詢(包括語句和參數(shù))時,會直接返回緩存中的對象,而不會去數(shù)據(jù)庫查詢。
- 如果 SqlSession 執(zhí)行了 增(INSERT)、刪(DELETE)、改(UPDATE) 操作,或者調(diào)用了
commit()、close()、rollback()方法,該 SqlSession 的一級緩存會被清空。這是為了防止讀取到臟數(shù)據(jù)。
3. 示例說明
// 假設(shè)獲取的 SqlSession 和 UserMapper
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查詢,會發(fā)送 SQL 到數(shù)據(jù)庫
User user1 = mapper.selectUserById(1L);
System.out.println(user1);
// 第二次查詢,SQL 和參數(shù)完全相同,直接從一級緩存返回,不查詢數(shù)據(jù)庫
User user2 = mapper.selectUserById(1L);
System.out.println(user2);
// 判斷是否為同一個對象(是,因為從緩存中返回的是同一個對象的引用)
System.out.println(user1 == user2); // 輸出:true
// 執(zhí)行一個更新操作
mapper.updateUser(user1);
// 此時,一級緩存被清空
// 第三次查詢,因為緩存被清空,會再次發(fā)送 SQL 到數(shù)據(jù)庫
User user3 = mapper.selectUserById(1L);
System.out.println(user3 == user1); // 輸出:false (雖然是同一條數(shù)據(jù),但已是新對象)
}4. 注意事項
- 對象相同:一級緩存返回的是 同一個對象的引用,因此在同一個 SqlSession 內(nèi),你操作的都是同一個 Java 對象。
- 分布式環(huán)境:一級緩存無法在多個應(yīng)用服務(wù)器之間共享,因為它綁定在單個請求的 SqlSession 上。
二級緩存
1. 作用域
- Mapper 級別 / Namespace 級別:多個 SqlSession 在訪問同一個 Mapper 的查詢時,可以共享其緩存。
- 它是 默認關(guān)閉 的,需要在全局配置中開啟,并在具體的 Mapper XML 中顯式配置。
2. 開啟與配置
a. 全局配置文件 (mybatis-config.xml):
必須顯式設(shè)置開啟二級緩存(雖然默認是 true,但顯式聲明是個好習(xí)慣)。
<configuration> <settings> <!-- 開啟全局二級緩存,默認就是 true,但建議寫明 --> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
b. Mapper XML 文件:
在需要開啟二級緩存的 Mapper.xml 中添加 <cache/> 標(biāo)簽。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 開啟本 Mapper 的二級緩存 -->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 其他 SQL 定義 -->
<select id="selectUserById" parameterType="long" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper><cache/>標(biāo)簽屬性:eviction:緩存回收策略。LRU(默認):最近最少使用。FIFO:先進先出。SOFT:軟引用,基于垃圾回收器狀態(tài)和軟引用規(guī)則移除。WEAK:弱引用,更積極地移除。flushInterval:緩存刷新間隔(毫秒),默認不清空。size:緩存存放多少元素。readOnly:是否為只讀。true:返回相同的緩存對象實例,性能好,但不允許修改。false(默認):通過序列化返回緩存對象的拷貝,安全,性能稍差。
3. 工作機制
- 當(dāng)一個 SqlSession 執(zhí)行查詢后,在關(guān)閉或提交時,其查詢結(jié)果會被存入二級緩存。
- 另一個 SqlSession 執(zhí)行相同的查詢時,會先從二級緩存中查找數(shù)據(jù)。如果找到,則直接返回,否則再去數(shù)據(jù)庫查詢。
- 任何一個 SqlSession 執(zhí)行了 增、刪、改 操作并
commit()后,會清空 整個對應(yīng) Mapper 的二級緩存,以保證數(shù)據(jù)一致性。
4. 示例說明
// 第一個 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1L); // 查詢數(shù)據(jù)庫
sqlSession1.close(); // 關(guān)閉時,數(shù)據(jù)存入二級緩存
}
// 第二個 SqlSession(與第一個不同)
try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
// 查詢相同的 SQL,直接從二級緩存獲取,不查詢數(shù)據(jù)庫
User user2 = mapper2.selectUserById(1L);
}
// 第三個 SqlSession,執(zhí)行了更新
try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
User user = mapper3.selectUserById(1L);
user.setName("New Name");
mapper3.updateUser(user); // 執(zhí)行更新
sqlSession3.commit(); // 提交時,清空 UserMapper 的二級緩存
}
// 第四個 SqlSession
try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {
UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class);
// 因為緩存已被清空,所以會再次查詢數(shù)據(jù)庫
User user4 = mapper4.selectUserById(1L);
}5. 注意事項
- 實體類序列化:如果二級緩存的
readOnly="false",那么對應(yīng)的實體類必須實現(xiàn)Serializable接口。 - 事務(wù)提交:只有在 SqlSession 執(zhí)行
commit()或close()時,數(shù)據(jù)才會從一級緩存轉(zhuǎn)存到二級緩存。 - 緩存粒度:二級緩存是 Mapper 級別的,有時會顯得比較粗粒度??梢酝ㄟ^
<cache-ref>讓多個 Mapper 共享一個緩存,但不推薦,容易引起數(shù)據(jù)混亂。
緩存順序與總結(jié)
當(dāng)發(fā)起一個查詢請求時,MyBatis 的緩存查詢順序是:
- 先查二級緩存:查看當(dāng)前 Mapper 的二級緩存中是否有數(shù)據(jù)。
- 再查一級緩存:如果二級緩存沒有,再查看當(dāng)前 SqlSession 的一級緩存中是否有數(shù)據(jù)。
- 最后查數(shù)據(jù)庫:如果兩級緩存都沒有,才發(fā)送 SQL 語句到數(shù)據(jù)庫執(zhí)行查詢。
查詢到的數(shù)據(jù)會 先存入一級緩存,在 SqlSession 關(guān)閉或提交時,再轉(zhuǎn)存到二級緩存。
特性 | 一級緩存 | 二級緩存 |
作用域 | SqlSession | Mapper (Namespace) |
默認狀態(tài) | 開啟 | 關(guān)閉 |
是否共享 | 否,Session 獨享 | 是,跨 Session 共享 |
清空時機 | UPDATE/INSERT/DELETE, | 同 Mapper 的 UPDATE/INSERT/DELETE + |
使用建議
- 查詢多,修改少的數(shù)據(jù)適合使用二級緩存,如字典表、配置項。
- 數(shù)據(jù)實時性要求高的場景(如交易、訂單)應(yīng)謹慎使用二級緩存,或者設(shè)置較短的刷新間隔。
- 在分布式環(huán)境中,默認的二級緩存(基于內(nèi)存)是無法共享的,需要集成 Redis、Ehcache 等第三方緩存中間件來替代。
- 理解緩存機制有助于解決一些“詭異”的問題,比如在同一個事務(wù)中,先后查詢和更新,但由于一級緩存的存在,后續(xù)查詢可能看不到其他線程的更新。
到此這篇關(guān)于Mybatis 的緩存機制的文章就介紹到這了,更多相關(guān)mybatis 緩存機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot使用Maven占位符@替換不生效問題及解決
這篇文章主要介紹了Springboot使用Maven占位符@替換不生效問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
SpringBoot項目啟動執(zhí)行任務(wù)的多種方法小結(jié)
這篇文章主要介紹了SpringBoot項目啟動執(zhí)行任務(wù)的多種方法小結(jié),本文給大家分享的這幾種方法經(jīng)常會被用到,當(dāng)我們的項目啟動后需要調(diào)用對應(yīng)的方法,用來項目的初始化等,本文通過示例代碼講解的非常詳細,需要的朋友參考下吧2023-07-07
解決mapper自動裝配識別不了,Could not autowire.No beans&
文章介紹了在使用MyBatisX插件和MybatisPlus自動生成代碼后,如何解決Spring Boot項目中自動注入`UserMapper`時報錯的問題,主要方法包括在主配置類或啟動類上添加`@MapperScan`注解,指定Mapper文件夾所在的包路徑,以及在Mapper類上添加`@Repository`注解2024-11-11
Maven通過filtering標(biāo)簽讀取變量配置的三種方法
在日常開發(fā)中,我們大多都會有開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境等,不同環(huán)境的參數(shù)肯定不一樣,本文主要介紹了Maven通過filtering標(biāo)簽讀取變量配置的三種方法,感興趣的可以了解一下2024-03-03

