MyBatis中Collection和Association的底層實(shí)現(xiàn)原理分析
引言
在 MyBatis 中,<collection> 和 <association> 標(biāo)簽用于處理一對(duì)多和一對(duì)一的關(guān)系。
這兩個(gè)標(biāo)簽在底層通過(guò)緩存、對(duì)象創(chuàng)建和反射機(jī)制,將數(shù)據(jù)庫(kù)結(jié)果集高效地映射為 Java 對(duì)象。
本文將重點(diǎn)探討這兩個(gè)標(biāo)簽的底層實(shí)現(xiàn)原理,并結(jié)合實(shí)際的數(shù)據(jù)庫(kù)示例說(shuō)明其工作機(jī)制。
1. Collection 的底層映射原理
1.1 一對(duì)多映射場(chǎng)景
<collection> 標(biāo)簽主要用于表示一對(duì)多關(guān)系的映射。
典型的一對(duì)多場(chǎng)景是在一個(gè)表中有重復(fù)的主對(duì)象記錄(如用戶),但每個(gè)主對(duì)象包含多個(gè)子對(duì)象(如訂單)。
示例數(shù)據(jù)庫(kù)結(jié)構(gòu)
假設(shè)我們有以下結(jié)果集,表示用戶和訂單的關(guān)系:
| user_id | username | order_id | order_date |
|---|---|---|---|
| 1 | Alice | 101 | 2023-01-01 |
| 1 | Alice | 102 | 2023-02-01 |
| 2 | Bob | 103 | 2023-03-01 |
這個(gè)數(shù)據(jù)表示,用戶 Alice 有兩個(gè)訂單,用戶 Bob 有一個(gè)訂單。我們希望將其映射為一個(gè) User 對(duì)象,每個(gè)用戶對(duì)象包含一個(gè)訂單集合。
MyBatis 映射配置
<resultMap id="userResultMap" type="com.example.User">
<id property="userId" column="user_id"/>
<result property="username" column="username"/>
<collection property="orders" ofType="com.example.Order">
<id property="orderId" column="order_id"/>
<result property="orderDate" column="order_date"/>
</collection>
</resultMap>
在這個(gè)映射中,<collection> 標(biāo)簽表示用戶對(duì)象 User 可能包含多個(gè) Order 對(duì)象,ofType 定義了集合中元素的類型。
1.2 底層映射機(jī)制
結(jié)果集處理
當(dāng)執(zhí)行 SQL 查詢時(shí),MyBatis 會(huì)獲取完整的結(jié)果集。由于用戶可能有多個(gè)訂單,查詢結(jié)果會(huì)包含重復(fù)的 user_id 和 username。
主對(duì)象緩存處理
MyBatis 使用主鍵 user_id 來(lái)判斷是否已經(jīng)創(chuàng)建了對(duì)應(yīng)的 User 對(duì)象(如果沒(méi)有主鍵, 會(huì)使用其他鍵來(lái)模擬唯一鍵)。
- 如果
user_id相同,表示當(dāng)前行屬于同一個(gè)用戶,則重用該用戶對(duì)象。 - 如果
user_id不同,MyBatis 會(huì)創(chuàng)建新的User對(duì)象。
集合屬性映射
對(duì)于每一行的訂單信息(order_id 和 order_date),MyBatis 會(huì)為當(dāng)前的用戶創(chuàng)建新的 Order 對(duì)象,并將其添加到用戶的 orders 集合中。
對(duì)象重用與集合管理
通過(guò)緩存機(jī)制,MyBatis 確保每個(gè)用戶只創(chuàng)建一次,而訂單則根據(jù)不同的 order_id 創(chuàng)建。對(duì)于相同的用戶,每個(gè)訂單行都會(huì)被添加到 orders 集合中。
底層邏輯代碼示例(偽代碼)
// 查詢用戶緩存處理
if (!cache.containsKey(userId)) {
User user = new User();
user.setUserId(resultSet.getInt("user_id"));
user.setUsername(resultSet.getString("username"));
cache.put(userId, user);
}
// 處理訂單集合
Order order = new Order();
order.setOrderId(resultSet.getInt("order_id"));
order.setOrderDate(resultSet.getDate("order_date"));
// 將訂單添加到用戶的集合
user.getOrders().add(order);
MyBatis 會(huì)緩存用戶對(duì)象,并通過(guò) getOrders().add(order) 方法將每個(gè)新創(chuàng)建的訂單對(duì)象加入到用戶的訂單集合中。
2. Association 的底層映射原理
2.1 一對(duì)一映射場(chǎng)景
<association> 標(biāo)簽用于處理一對(duì)一或多對(duì)一的映射。在這種場(chǎng)景下,通常我們希望將兩個(gè)表的結(jié)果組合為一個(gè)主對(duì)象和一個(gè)關(guān)聯(lián)對(duì)象。例如,用戶和用戶詳情是一對(duì)一的關(guān)系。
示例數(shù)據(jù)庫(kù)結(jié)構(gòu)
假設(shè)我們有以下結(jié)果集,表示用戶及其詳細(xì)信息:
| user_id | username | detail_id | age | address |
|---|---|---|---|---|
| 1 | Alice | 1 | 25 | Wonderland |
| 2 | Bob | 2 | 30 | Wonderland |
這個(gè)數(shù)據(jù)表示,每個(gè)用戶有一條與之關(guān)聯(lián)的詳細(xì)信息記錄。我們希望將其映射為 User 對(duì)象,其中包含一個(gè) UserDetail 對(duì)象。
MyBatis 映射配置
<resultMap id="userWithDetailResultMap" type="com.example.User">
<id property="userId" column="user_id"/>
<result property="username" column="username"/>
<association property="detail" javaType="com.example.UserDetail">
<id property="detailId" column="detail_id"/>
<result property="age" column="age"/>
<result property="address" column="address"/>
</association>
</resultMap>
在這個(gè)映射中,<association> 標(biāo)簽用于將用戶與詳細(xì)信息關(guān)聯(lián)起來(lái),javaType 指定了 UserDetail 對(duì)象的類型。
2.2 底層映射機(jī)制
主對(duì)象創(chuàng)建與緩存
當(dāng)執(zhí)行查詢時(shí),MyBatis 會(huì)首先根據(jù) user_id 創(chuàng)建或查找緩存中的用戶對(duì)象。類似于 <collection>,如果用戶對(duì)象已經(jīng)存在,則不會(huì)重新創(chuàng)建。
關(guān)聯(lián)對(duì)象的映射
對(duì)于每一行,MyBatis 根據(jù) detail_id 創(chuàng)建 UserDetail 對(duì)象,并通過(guò) MetaObject 機(jī)制,將查詢結(jié)果的 age 和 address 列映射到 UserDetail 對(duì)象中。
關(guān)聯(lián)對(duì)象賦值
一旦 UserDetail 對(duì)象創(chuàng)建完成,MyBatis 會(huì)將其賦值給 User 對(duì)象的 detail 屬性。這是通過(guò)反射完成的。
底層邏輯代碼示例(偽代碼)
// 用戶緩存處理
User user = cache.get(userId);
// 創(chuàng)建并映射用戶詳細(xì)信息
UserDetail detail = new UserDetail();
detail.setDetailId(resultSet.getInt("detail_id"));
detail.setAge(resultSet.getInt("age"));
detail.setAddress(resultSet.getString("address"));
// 將詳細(xì)信息賦值給用戶對(duì)象
user.setDetail(detail);
MyBatis 在映射時(shí)會(huì)通過(guò) MetaObject 訪問(wèn) User 對(duì)象的 detail 屬性,并使用反射為其賦值。這個(gè)過(guò)程使得每個(gè)用戶對(duì)象都能夠正確關(guān)聯(lián)其詳細(xì)信息。
3. 總結(jié)
MyBatis 中的 <collection> 和 <association> 標(biāo)簽通過(guò)緩存、反射與對(duì)象創(chuàng)建機(jī)制,完成數(shù)據(jù)庫(kù)結(jié)果集到 Java 對(duì)象的映射。在處理一對(duì)多和一對(duì)一關(guān)系時(shí),這兩個(gè)標(biāo)簽的底層機(jī)制可以高效地組織復(fù)雜的對(duì)象結(jié)構(gòu)。
<collection>標(biāo)簽:通過(guò)主對(duì)象的緩存和子對(duì)象集合的動(dòng)態(tài)添加,實(shí)現(xiàn)了復(fù)雜的一對(duì)多關(guān)系的映射。<association>標(biāo)簽:通過(guò)關(guān)聯(lián)對(duì)象的創(chuàng)建和屬性映射,實(shí)現(xiàn)了一對(duì)一或多對(duì)一的關(guān)聯(lián)映射。
理解這些底層機(jī)制能夠幫助開(kāi)發(fā)者優(yōu)化查詢性能,并更好地設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),充分發(fā)揮 MyBatis 的 ORM 功能。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- MyBatis中高級(jí)多表查詢(ResultMap、association、collection)詳解
- MyBatis使用嵌套查詢collection和association的實(shí)現(xiàn)
- mybatis中association和collection的使用與區(qū)別
- MyBatis的collection和association的使用解讀
- Mybatis中一對(duì)多(collection)和一對(duì)一(association)的組合查詢使用
- MyBatis中的ResultMap的association和collection標(biāo)簽詳解
- mybatis?collection和association的區(qū)別解析
相關(guān)文章
Java Lambda表達(dá)式的方法引用和構(gòu)造器引用實(shí)例分析
這篇文章主要介紹了Java Lambda表達(dá)式的方法引用和構(gòu)造器引用,結(jié)合實(shí)例形式分析了Lambda表達(dá)式的方法引用和構(gòu)造器引用相關(guān)原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2019-09-09
Spring,hibernate,struts經(jīng)典面試筆試題(含答案)
這篇文章主要介紹了Spring,hibernate,struts經(jīng)典面試筆試題極其參考含答案,涉及SSH基本概念,原理與使用技巧,需要的朋友可以參考下2016-03-03
Spring如何實(shí)現(xiàn)輸出帶動(dòng)態(tài)標(biāo)簽的日志
文章介紹了如何通過(guò)動(dòng)態(tài)標(biāo)簽日志實(shí)現(xiàn),解決了部分業(yè)務(wù)代碼在多個(gè)模塊中調(diào)用時(shí)日志無(wú)法直觀看出來(lái)源的問(wèn)題,主要通過(guò)ThreadLocal存儲(chǔ)業(yè)務(wù)標(biāo)簽,并在日志輸出時(shí)插入該標(biāo)簽,實(shí)現(xiàn)日志的動(dòng)態(tài)標(biāo)簽功能,感興趣的朋友一起看看吧2024-12-12
Java BufferedReader相關(guān)源碼實(shí)例分析
這篇文章主要介紹了Java BufferedReader相關(guān)源碼實(shí)例分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
jdk17+springboot使用webservice的踩坑實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于jdk17+springboot使用webservice踩坑的相關(guān)資料,網(wǎng)上很多教程是基于jdk8的,所以很多在17上面跑不起來(lái),折騰兩天,直接給答案,需要的朋友可以參考下2024-01-01
Springboot如何通過(guò)filter修改Header的值
這篇文章主要介紹了Springboot如何通過(guò)filter修改Header的值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java非法字符: ‘\ufeff‘問(wèn)題及說(shuō)明
這篇文章主要介紹了Java非法字符: ‘\ufeff‘問(wèn)題及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02

