MyBatis 延遲加載與緩存的實(shí)現(xiàn)
一、延遲加載策略:按需加載,優(yōu)化性能
1. 延遲加載 vs 立即加載:核心區(qū)別
- 立即加載:主查詢(如查詢用戶)執(zhí)行時,主動關(guān)聯(lián)加載關(guān)聯(lián)數(shù)據(jù)(如用戶的所有賬號)。
- 場景:多對一查詢(如賬號關(guān)聯(lián)用戶),需立即獲取關(guān)聯(lián)數(shù)據(jù)。
- 延遲加載:主查詢執(zhí)行時暫不加載關(guān)聯(lián)數(shù)據(jù),僅當(dāng)程序訪問關(guān)聯(lián)數(shù)據(jù)時,再觸發(fā)子查詢。
- 場景:一對多查詢(如用戶關(guān)聯(lián)多個賬號),減少初始查詢壓力。
舉個小例子:
場景:查詢用戶信息時不立即加載其訂單,僅在需要查看訂單時再觸發(fā)查詢。
示例:電商用戶詳情頁先展示用戶姓名、地址,點(diǎn)擊 “查看訂單” 按鈕時,才加載該用戶的訂單列表。
2. 多對一延遲加載實(shí)現(xiàn)(Account → User)
步驟解析:
1、定義關(guān)聯(lián)查詢:主查詢僅查賬號表,關(guān)聯(lián)用戶信息通過子查詢延遲加載。
<!-- 主查詢:僅查賬號 -->
<select id="findAll" resultMap="accountMap">
SELECT * FROM account
</select>
<!-- 子查詢:通過用戶ID查用戶信息 -->
<select id="findById" parameterType="int" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>2、配置延遲加載:通過 association 標(biāo)簽指定子查詢路徑和參數(shù)。
<resultMap type="Account" id="accountMap">
<association
property="user" <!-- Account類中的User屬性 -->
javaType="User" <!-- 關(guān)聯(lián)對象類型 -->
select="findById" <!-- 子查詢方法名 -->
column="uid" <!-- 主查詢結(jié)果中用于關(guān)聯(lián)的列(賬號表的uid) -->
/>
</resultMap>3、全局開啟延遲加載:在 SqlMapConfig.xml 中配置。
<settings> <setting name="lazyLoadingEnabled" value="true"/> <!-- 開啟延遲加載 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 關(guān)閉積極加載(默認(rèn)會加載所有關(guān)聯(lián)數(shù)據(jù)) --> </settings>
測試驗(yàn)證:
@Test
public void testLazyLoading() {
List<Account> accounts = accountMapper.findAll();
for (Account account : accounts) {
System.out.println("賬號金額:" + account.getMoney()); // 主查詢執(zhí)行時僅輸出金額
System.out.println("用戶名稱:" + account.getUser().getUsername()); // 首次訪問user時觸發(fā)子查詢
}
}3. 一對多延遲加載實(shí)現(xiàn)(User → Accounts)
核心配置:
<!-- 主查詢:僅查用戶表 -->
<select id="findAll" resultMap="userMap">
SELECT * FROM user
</select>
<resultMap type="User" id="userMap">
<collection
property="accounts" <!-- User類中的賬號列表屬性 -->
ofType="Account" <!-- 集合元素類型 -->
select="com.qcbyjy.mapper.AccountMapper.findByUid" <!-- 子查詢:通過用戶ID查賬號 -->
column="id" <!-- 主查詢結(jié)果中的用戶ID -->
/>
</resultMap>
<!-- 子查詢:根據(jù)用戶ID查賬號 -->
<select id="findByUid" parameterType="int" resultType="Account">
SELECT * FROM account WHERE uid = #{uid}
</select>關(guān)鍵區(qū)別:
- 多對一用
association(單個對象),一對多用collection(集合)。 - 子查詢參數(shù)通過
column傳遞主查詢結(jié)果中的字段(如用戶表的id)。
二、MyBatis 緩存機(jī)制:減少數(shù)據(jù)庫訪問
1. 緩存的核心價值
- 定義:將頻繁查詢的數(shù)據(jù)臨時存儲在內(nèi)存中,避免重復(fù)訪問數(shù)據(jù)庫,提升查詢速度。
- 適用場景:讀多寫少、數(shù)據(jù)更新不頻繁的數(shù)據(jù)(如字典表、配置信息)。
2. 一級緩存:SqlSession 級別的緩存
(1)本質(zhì)與作用
- 作用域:基于
SqlSession對象,同一SqlSession內(nèi)的相同查詢會直接從緩存獲取結(jié)果。 - 實(shí)現(xiàn)原理:
SqlSession內(nèi)部維護(hù)一個HashMap,鍵為查詢的唯一標(biāo)識(SQL + 參數(shù)),值為查詢結(jié)果對象。
(2)驗(yàn)證一級緩存
@Test
public void testFirstLevelCache() {
// 同一 SqlSession 內(nèi)的兩次相同查詢
User user1 = userMapper.findById(1);
User user2 = userMapper.findById(1); // 直接從緩存獲取,不執(zhí)行 SQL
System.out.println(user1 == user2); // 輸出 true(對象引用相同)
}(3)緩存失效場景
SqlSession關(guān)閉或提交(commit)。- 執(zhí)行
update/insert/delete操作(會清空緩存)。 - 手動調(diào)用
session.clearCache()清空緩存。
3. 二級緩存:SqlSessionFactory 級別的緩存
(1)核心概念
- 作用域:基于
SqlSessionFactory,跨SqlSession共享緩存(如多個SqlSession執(zhí)行相同查詢)。 - 實(shí)現(xiàn)條件:
實(shí)體類需實(shí)現(xiàn)
Serializable接口(支持序列化存儲)。在
SqlMapConfig.xml中開啟二級緩存(默認(rèn)已開啟)。在 Mapper 中配置
<cache/>標(biāo)簽。
(2)配置步驟
1、 實(shí)體類實(shí)現(xiàn)序列化:
public class User implements Serializable {
// 省略屬性和方法
}2、Mapper 中啟用緩存:
<mapper namespace="com.qcbyjy.mapper.UserMapper">
<cache/> <!-- 啟用二級緩存 -->
<select id="findById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>3、配置緩存策略(可選):
<cache eviction="LRU" <!-- 緩存淘汰策略:LRU(最近最少使用) --> flushInterval="60000" <!-- 自動刷新間隔(毫秒) --> size="512" <!-- 最大緩存對象數(shù) --> readOnly="true" <!-- 是否只讀:true(共享對象)/ false(復(fù)制對象) --> />
(3)緩存優(yōu)先級與刷新
- 優(yōu)先級:二級緩存 > 一級緩存 > 數(shù)據(jù)庫查詢。
- 刷新機(jī)制:執(zhí)行
update/insert/delete時,會清空對應(yīng) Mapper 的二級緩存。
(4)測試驗(yàn)證
@Test
public void testSecondLevelCache() {
try (SqlSession session1 = factory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.findById(1); // 首次查詢,命中數(shù)據(jù)庫
}
try (SqlSession session2 = factory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.findById(1); // 第二次查詢,命中二級緩存,不執(zhí)行 SQL
}
}三、總結(jié):性能優(yōu)化核心要點(diǎn)
| 技術(shù) | 核心作用 | 關(guān)鍵配置 |
| 延遲加載 | 減少初始查詢數(shù)據(jù)量,提升響應(yīng)速度 | lazyLoadingEnabled、association/collection 的 select 屬性 |
| 一級緩存 | 減少同一會話內(nèi)的重復(fù)查詢 | 自動生效,無需額外配置(注意 SqlSession 生命周期) |
| 二級緩存 | 跨會話共享緩存,減少數(shù)據(jù)庫壓力 | 實(shí)體類序列化、<cache/> 標(biāo)簽、緩存策略配置 |
合理運(yùn)用延遲加載和緩存,能顯著提升 MyBatis 應(yīng)用的性能,但需根據(jù)業(yè)務(wù)場景靈活選擇,避免過度使用導(dǎo)致數(shù)據(jù)不一致或內(nèi)存溢出。
到此這篇關(guān)于MyBatis 延遲加載與緩存的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)MyBatis 延遲加載與緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis中注解與xml配置的對應(yīng)關(guān)系和對比分析
這篇文章主要介紹了mybatis中注解與xml配置的對應(yīng)關(guān)系和對比分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Java?事務(wù)注解@Transactional回滾(try?catch、嵌套)問題
這篇文章主要介紹了Java?@Transactional回滾(try?catch、嵌套)問題,Spring?事務(wù)注解?@Transactional?本來可以保證原子性,如果事務(wù)內(nèi)有報(bào)錯的話,整個事務(wù)可以保證回滾,但是加上try?catch或者事務(wù)嵌套,可能會導(dǎo)致事務(wù)回滾失敗2022-08-08
基于SpringBoot創(chuàng)建Web頁面并熱更新的操作步驟
SpringBoot是一個用于快速開發(fā)單個微服務(wù)的框架,它基于 Spring 框架,簡化了Spring應(yīng)用的初始化過程和開發(fā)流程,本文給大家介紹了如何基于SpringBoot創(chuàng)建Web頁面并熱更新,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11

