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

