mybatis中的延遲加載和一二級緩存深入解析
MyBatis 延遲加載與一二級緩存核心知識點
一、延遲加載(Lazy Loading)
1. 核心定義
延遲加載是 MyBatis 對關聯(lián)查詢的優(yōu)化機制:查詢主對象時,不立即查詢其關聯(lián)對象(如用戶和賬戶的一對多關系),僅在實際使用關聯(lián)對象(調用 getter 方法)時,才觸發(fā)關聯(lián)查詢的 SQL 執(zhí)行。
2. 核心對比(延遲加載 vs 立即加載)
| 加載方式 | 執(zhí)行時機 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|---|
| 延遲加載 | 調用關聯(lián)對象 getter 時 | 關聯(lián)數(shù)據(jù)不常使用、關聯(lián)表數(shù)據(jù)量大 | 減少無效查詢,提升性能 | 可能觸發(fā) N+1 問題(循環(huán)查詢時) |
| 立即加載 | 查詢主對象時(如 LEFT JOIN 關聯(lián)) | 關聯(lián)數(shù)據(jù)必用、數(shù)據(jù)量小 | 一次查詢完成,避免多次數(shù)據(jù)庫交互 | 冗余數(shù)據(jù)加載,性能損耗 |
3. 實現(xiàn)原理
基于 動態(tài)代理:查詢主對象時,MyBatis 返回主對象的代理實例;當調用關聯(lián)對象的 getter 方法時,代理對象攔截該調用,觸發(fā)關聯(lián)查詢 SQL 執(zhí)行,查詢結果賦值后返回。
4. 配置要求(全局開啟)
需在 SqlMapConfig.xml 的 <settings> 標簽中配置(MyBatis 默認為關閉):
<settings>
<!-- 全局開啟延遲加載(核心) -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 關閉積極加載(MyBatis 3.4.1+ 默認 false,低版本需手動設置)
避免一次性加載所有關聯(lián)對象 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 可選:指定觸發(fā)延遲加載的方法(默認 equals/clone/hashCode/toString)
調用這些方法不會觸發(fā)延遲加載 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>5. 關聯(lián)查詢配置(XML 示例)
一對多(用戶 -> 賬戶)
<!-- UserMapper.xml -->
<resultMap id="userAccountMap" type="cn.tx.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<!-- collection 配置一對多關聯(lián),select 指定關聯(lián)查詢方法 -->
<collection
property="accounts" <!-- 主對象中關聯(lián)屬性名 -->
ofType="cn.tx.domain.Account" <!-- 關聯(lián)對象類型 -->
column="id" <!-- 關聯(lián)條件(主表主鍵 -> 從表外鍵) -->
select="cn.tx.mapper.AccountMapper.findAccountsByUid"/> <!-- 關聯(lián)查詢 SQL -->
</resultMap>
<!-- 主查詢:僅查詢用戶,不加載賬戶 -->
<select id="findUserById" resultMap="userAccountMap">
SELECT id, username FROM user WHERE id = #{id}
</select>一對一(用戶 -> 身份證)
<resultMap id="userIdCardMap" type="cn.tx.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<!-- association 配置一對一關聯(lián) -->
<association
property="idCard"
javaType="cn.tx.domain.IdCard" <!-- 一對一用 javaType -->
column="id"
select="cn.tx.mapper.IdCardMapper.findByIdCardByUid"/>
</resultMap>6. 關鍵注意事項
- SqlSession 存活要求:延遲加載需通過
SqlSession執(zhí)行關聯(lián)查詢,因此調用getter前,SqlSession不能關閉(否則報PersistenceException)。 - N+1 問題規(guī)避:循環(huán)查詢多個主對象時,避免逐個觸發(fā)關聯(lián)查詢(1 次主查詢 + N 次關聯(lián)查詢);大量數(shù)據(jù)建議用
LEFT JOIN立即加載。 - 實體類無特殊要求:無需實現(xiàn)接口,僅需保證關聯(lián)屬性有
getter/setter方法(代理對象需通過getter觸發(fā)加載)。
二、MyBatis 緩存機制(一級緩存 + 二級緩存)
MyBatis 緩存的核心目的是減少數(shù)據(jù)庫查詢次數(shù),提升性能,分為一級緩存(本地緩存)和二級緩存(全局緩存)。
1. 一級緩存(Local Cache)
(1)核心定義
- 作用范圍:單個 SqlSession 內(nèi)部(會話級緩存),不同 SqlSession 互不共享。
- 存儲介質:內(nèi)存(HashMap),默認開啟,無需額外配置。
(2)工作流程
- 同一
SqlSession執(zhí)行相同查詢(相同 SQL + 參數(shù)):- 首次查詢:查數(shù)據(jù)庫,結果存入一級緩存。
- 二次查詢:直接從緩存獲取,不執(zhí)行 SQL。
- 觸發(fā)緩存清空的場景:
- 執(zhí)行
insert/update/delete操作(自動清空當前 SqlSession 的一級緩存,保證數(shù)據(jù)一致性)。 - 調用
sqlSession.clearCache()手動清空。 - SqlSession 關閉(緩存失效)。
- 執(zhí)行
(3)配置說明
默認開啟,可通過 localCacheScope 調整作用域(全局配置):
<settings>
<!-- SESSION(默認):緩存作用于整個 SqlSession -->
<!-- STATEMENT:緩存僅作用于當前 SQL 語句,執(zhí)行后立即清空 -->
<setting name="localCacheScope" value="SESSION"/>
</settings>2. 二級緩存(Second Level Cache)
(1)核心定義
- 作用范圍:全局共享(跨 SqlSession),按 Mapper 接口(
namespace)隔離(同一 namespace 共享緩存)。 - 存儲介質:默認內(nèi)存(HashMap),可集成 Redis/Ehcache 等第三方緩存(持久化)。
- 依賴要求:緩存的實體類必須實現(xiàn)
Serializable接口(緩存對象需序列化存儲)。
(2)工作流程
- 開啟二級緩存后,
SqlSession關閉時,一級緩存中的數(shù)據(jù)會寫入二級緩存。 - 新
SqlSession執(zhí)行相同查詢(同一 namespace + 相同 SQL + 參數(shù)):- 先查二級緩存,命中則返回。
- 未命中則查數(shù)據(jù)庫,結果存入一級緩存,
SqlSession關閉后同步到二級緩存。
- 觸發(fā)緩存清空的場景:
- 同一 namespace 下執(zhí)行
insert/update/delete操作(自動清空當前 namespace 的二級緩存)。 - 配置
flushInterval自動刷新(如 60 秒)。 - 手動調用
sqlSessionFactory.getConfiguration().getCache(namespace).clear()。
- 同一 namespace 下執(zhí)行
(3)完整配置步驟
步驟 1:實體類實現(xiàn) Serializable
public class User implements Serializable { // 必須實現(xiàn),否則緩存序列化失敗
private Integer id;
private String username;
private List<Account> accounts;
// getter/setter/toString
}步驟 2:全局開啟二級緩存(SqlMapConfig.xml)
<settings>
<!-- 全局開啟二級緩存(默認 true,顯式配置更清晰) -->
<setting name="cacheEnabled" value="true"/>
<!-- 可選:全局緩存自動刷新時間(毫秒),默認不自動刷新 -->
<setting name="cacheFlushInterval" value="60000"/>
</settings>步驟 3:Mapper 開啟緩存(XML 方式)
在 Mapper XML 的 mapper 根標簽下添加 <cache> 標簽:
<!-- UserMapper.xml -->
<mapper namespace="cn.tx.mapper.UserMapper">
<!-- 開啟當前 namespace 的二級緩存 -->
<cache
eviction="LRU" <!-- 緩存回收策略(默認 LRU) -->
flushInterval="60000" <!-- 60 秒自動刷新 -->
size="1024" <!-- 緩存最大容量(默認 1024 個對象) -->
readOnly="false"/> <!-- false:可修改(返回副本);true:只讀(返回原對象,性能高) -->
<!-- 查詢方法:默認 useCache="true"(啟用二級緩存) -->
<select id="findUserById" resultMap="userAccountMap" useCache="true">
SELECT id, username FROM user WHERE id = #{id}
</select>
<!-- 增刪改:默認 flushCache="true"(清空緩存),可省略 -->
<update id="updateUser" flushCache="true">
UPDATE user SET username = #{username} WHERE id = #{id}
</update>
</mapper>步驟 4:注解方式配置(備選)
// UserMapper.java(注解開啟二級緩存)
@CacheNamespace(
implementation = PerpetualCache.class, // 緩存實現(xiàn)類(默認)
eviction = LruCache.class, // 回收策略
flushInterval = 60000,
size = 1024,
readWrite = true // 等價于 readOnly="false"
)
public interface UserMapper {
@Options(useCache = true) // 啟用二級緩存
User findUserById(@Param("id") Integer id);
@Options(flushCache = true) // 清空緩存
void updateUser(User user);
}(4)<cache>標簽核心屬性
| 屬性 | 取值說明 |
|---|---|
eviction | 緩存回收策略(4 種):- LRU(默認):最近最少使用,移除最長時間未使用對象- FIFO:先進先出,按存入順序移除- SOFT:軟引用,內(nèi)存不足時移除- WEAK:弱引用,垃圾回收時移除 |
flushInterval | 自動刷新時間(毫秒),默認不自動刷新(僅增刪改觸發(fā)) |
size | 緩存最大對象數(shù)(默認 1024),需根據(jù)內(nèi)存調整 |
readOnly | false(默認):緩存對象可修改(返回序列化副本);true:只讀(返回原對象,性能更高) |
3. 一二級緩存核心對比
| 特性 | 一級緩存(Local Cache) | 二級緩存(Second Level Cache) |
|---|---|---|
| 作用范圍 | 單個 SqlSession | 全局(跨 SqlSession),按 namespace 隔離 |
| 開啟方式 | 默認開啟,無需配置 | 全局開關 + Mapper 單獨開啟 |
| 存儲介質 | 內(nèi)存(HashMap) | 默認內(nèi)存,可集成第三方緩存 |
| 序列化要求 | 無 | 實體類必須實現(xiàn) Serializable |
| 數(shù)據(jù)一致性 | 會話內(nèi)一致(自動清空) | 跨會話一致(增刪改自動清空) |
| 適用場景 | 單次會話內(nèi)重復查詢 | 多會話共享數(shù)據(jù)(如字典、靜態(tài)數(shù)據(jù)) |
4. 緩存使用注意事項
- 關聯(lián)查詢緩存一致性:如果 Mapper A 關聯(lián) Mapper B 的查詢,建議 Mapper B 也開啟二級緩存,避免關聯(lián)數(shù)據(jù)緩存不一致。
- 禁用部分查詢緩存:實時性要求高的數(shù)據(jù)(如訂單狀態(tài)),可通過
useCache="false"禁用二級緩存:
<select id="findRealTimeOrder" resultType="Order" useCache="false">
SELECT * FROM order WHERE id = #{id}
</select>- 第三方緩存集成:生產(chǎn)環(huán)境建議用 Redis/Ehcache 替代默認內(nèi)存緩存(默認緩存重啟項目失效),需添加對應依賴(如
mybatis-redis)并配置緩存實現(xiàn)類。 - 避免緩存臟數(shù)據(jù):多表關聯(lián)查詢(跨 namespace)不建議用二級緩存,容易因某張表更新未觸發(fā)其他 namespace 緩存清空,導致臟數(shù)據(jù)。
三、延遲加載與緩存的協(xié)同工作
- 延遲加載的關聯(lián)查詢也會觸發(fā)緩存:首次調用
getter執(zhí)行關聯(lián)查詢后,結果會存入一級緩存,SqlSession關閉后同步到二級緩存。 - 緩存優(yōu)先級:二級緩存 > 一級緩存 > 數(shù)據(jù)庫(查詢時先查二級緩存,再查一級緩存,最后查數(shù)據(jù)庫)。
- 協(xié)同優(yōu)化場景:查詢主對象(如用戶)時用延遲加載(避免關聯(lián)數(shù)據(jù)冗余),主對象和關聯(lián)對象均開啟二級緩存(減少重復查詢),適合 “主數(shù)據(jù)不常變、關聯(lián)數(shù)據(jù)按需加載” 的場景(如用戶信息 + 歷史訂單)。
到此這篇關于mybatis中的延遲加載和一二級緩存深入解析的文章就介紹到這了,更多相關mybatis延遲加載和緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
"Method?Not?Allowed"405問題分析以及解決方法
項目中在提交表單時,提示“HTTP 405”錯誤——“Method Not Allowed”這里顯示的是,方法不被允許,下面這篇文章主要給大家介紹了關于"Method?Not?Allowed"405問題分析以及解決方法的相關資料,需要的朋友可以參考下2022-10-10
解決Springboot不能自動提交數(shù)據(jù)庫連接問題
在使用SSM框架開發(fā)時,若在同一Service內(nèi)部方法間互相調用,直接使用this關鍵字會導致事務管理失效,從而引發(fā)如數(shù)據(jù)庫連接不足等問題,原因是通過this調用不會經(jīng)過Spring的代理,因此不會自動進行事務處理2024-09-09

