MyBatis的關(guān)聯(lián)查詢實現(xiàn)(一對一、一對多、多對多)
實際開發(fā)中數(shù)據(jù)庫表之間往往存在關(guān)聯(lián)關(guān)系(如用戶與訂單、訂單與商品),MyBatis的關(guān)聯(lián)查詢用于處理這些關(guān)系,將多表數(shù)據(jù)映射為Java對象的關(guān)聯(lián)關(guān)系,相比JDBC手動處理結(jié)果集拼接,MyBatis通過resultMap的association和collection標(biāo)簽,能自動完成關(guān)聯(lián)數(shù)據(jù)的映射。本文我將系統(tǒng)講解MyBatis關(guān)聯(lián)查詢的核心實現(xiàn),包括一對一、一對多、多對多關(guān)系,并結(jié)合實例解析查詢方式與優(yōu)化技巧。
一、關(guān)聯(lián)查詢的基本概念
1.1 數(shù)據(jù)庫表關(guān)聯(lián)關(guān)系
數(shù)據(jù)庫表的關(guān)聯(lián)關(guān)系主要有三種:
- 一對一:A表一條記錄對應(yīng)B表一條記錄(如用戶與身份證,一個用戶對應(yīng)一個身份證);
- 一對多:A表一條記錄對應(yīng)B表多條記錄(如用戶與訂單,一個用戶可有多筆訂單);
- 多對多:A表多條記錄對應(yīng)B表多條記錄(如學(xué)生與課程,一個學(xué)生可選多門課程,一門課程可有多個學(xué)生),通常通過中間表實現(xiàn)。
1.2 MyBatis關(guān)聯(lián)查詢的核心
MyBatis通過resultMap實現(xiàn)關(guān)聯(lián)查詢,核心標(biāo)簽:
association:映射一對一關(guān)系(如用戶對象中包含一個身份證對象);collection:映射一對多或多對多關(guān)系(如用戶對象中包含一個訂單列表)。
關(guān)聯(lián)查詢有兩種實現(xiàn)方式:
- 嵌套查詢:先查詢主表數(shù)據(jù),再根據(jù)主表字段查詢關(guān)聯(lián)表(多輪查詢);
- 連接查詢:通過
JOIN語句一次性查詢多表數(shù)據(jù)(單輪查詢)。
后續(xù)將通過案例詳細(xì)對比這兩種方式的優(yōu)缺點。
二、一對一關(guān)聯(lián)查詢
以“用戶(user)與身份證(id_card)”為例,一個用戶對應(yīng)一個身份證,實現(xiàn)一對一關(guān)聯(lián)查詢。
2.1 數(shù)據(jù)庫表設(shè)計
-- 用戶表 CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 身份證表(與用戶一對一關(guān)聯(lián)) CREATE TABLE `id_card` ( `id` int NOT NULL AUTO_INCREMENT, `card_no` varchar(20) NOT NULL, -- 身份證號 `user_id` int NOT NULL, -- 關(guān)聯(lián)用戶ID(外鍵) PRIMARY KEY (`id`), UNIQUE KEY `user_id` (`user_id`) -- 一對一:user_id唯一 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 實體類設(shè)計
// 用戶類(包含一個身份證對象)
@Data
public class User {
private Integer id;
private String username;
private Integer age;
// 一對一關(guān)聯(lián):用戶包含一個身份證
private IdCard idCard;
}
// 身份證類
@Data
public class IdCard {
private Integer id;
private String cardNo;
private Integer userId;
}
2.3 一對一查詢實現(xiàn)
2.3.1 方式1:連接查詢(推薦)
通過JOIN語句一次性查詢用戶和身份證數(shù)據(jù),再通過association映射關(guān)聯(lián)對象。
Mapper接口:
// 根據(jù)用戶ID查詢用戶及關(guān)聯(lián)的身份證 User selectUserWithIdCardById(Integer id);
Mapper XML:
<!-- 定義resultMap:映射用戶及身份證 -->
<resultMap id="UserWithIdCardMap" type="User">
<!-- 用戶表字段映射 -->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 一對一關(guān)聯(lián):association映射IdCard對象 -->
<association property="idCard" javaType="IdCard">
<id column="card_id" property="id"/> <!-- 注意:避免與user.id字段沖突 -->
<result column="card_no" property="cardNo"/>
<result column="user_id" property="userId"/>
</association>
</resultMap>
<!-- 連接查詢:一次性查詢用戶和身份證 -->
<select id="selectUserWithIdCardById" resultMap="UserWithIdCardMap">
SELECT
u.id, u.username, u.age,
ic.id AS card_id, ic.card_no, ic.user_id
FROM user u
LEFT JOIN id_card ic ON u.id = ic.user_id
WHERE u.id = #{id}
</select>
核心說明:
association的property:對應(yīng)User類中的idCard屬性;javaType:指定關(guān)聯(lián)對象的類型(IdCard);- 表連接時需通過
AS為關(guān)聯(lián)表字段起別名(如ic.id AS card_id),避免與主表字段(u.id)沖突。
2.3.2 方式2:嵌套查詢
先查詢用戶數(shù)據(jù),再通過用戶ID查詢身份證(分兩次查詢)。
步驟1:查詢身份證的Mapper
// IdCardMapper接口 IdCard selectById(Integer id);
<select id="selectById" resultType="IdCard">
SELECT id, card_no, user_id FROM id_card WHERE id = #{id}
</select>
步驟2:查詢用戶并嵌套查詢身份證
<resultMap id="UserWithIdCardNestedMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 嵌套查詢:通過select屬性指定查詢關(guān)聯(lián)對象的方法 -->
<association
property="idCard"
javaType="IdCard"
column="id" <!-- 將用戶id作為參數(shù)傳遞給關(guān)聯(lián)查詢 -->
select="com.example.mapper.IdCardMapper.selectByUserId"/> <!-- 關(guān)聯(lián)查詢的Mapper方法 -->
</resultMap>
<select id="selectUserWithIdCardNestedById" resultMap="UserWithIdCardNestedMap">
SELECT id, username, age FROM user WHERE id = #{id}
</select>
核心說明:
association的select:指定查詢關(guān)聯(lián)對象的Mapper方法(全類名+方法名);column:將主查詢的id(用戶ID)作為參數(shù)傳遞給selectByUserId方法。
2.3.3 兩種方式對比
| 方式 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| 連接查詢 | 單輪查詢,性能好 | SQL較復(fù)雜(多表JOIN) | 關(guān)聯(lián)數(shù)據(jù)必須查詢,且表數(shù)據(jù)量不大 |
| 嵌套查詢 | SQL簡單,邏輯清晰 | 多輪查詢(N+1問題),性能較差 | 關(guān)聯(lián)數(shù)據(jù)可選查詢(按需加載) |
三、一對多關(guān)聯(lián)查詢
以“用戶(user)與訂單(order)”為例,一個用戶可有多筆訂單,實現(xiàn)一對多關(guān)聯(lián)查詢。
3.1 數(shù)據(jù)庫表設(shè)計
-- 訂單表(與用戶一對多關(guān)聯(lián)) CREATE TABLE `order` ( `id` int NOT NULL AUTO_INCREMENT, `order_no` varchar(20) NOT NULL, -- 訂單號 `total_amount` decimal(10,2) NOT NULL, -- 總金額 `user_id` int NOT NULL, -- 關(guān)聯(lián)用戶ID PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 實體類設(shè)計
// 用戶類(包含訂單列表)
@Data
public class User {
private Integer id;
private String username;
private Integer age;
// 一對多關(guān)聯(lián):用戶包含多個訂單
private List<Order> orders;
}
// 訂單類
@Data
public class Order {
private Integer id;
private String orderNo;
private BigDecimal totalAmount;
private Integer userId;
}
3.3 一對多查詢實現(xiàn)
以連接查詢為例(推薦,單輪查詢性能更好):
Mapper接口:
// 查詢用戶及關(guān)聯(lián)的所有訂單 User selectUserWithOrdersById(Integer id);
Mapper XML:
<!-- 定義resultMap:映射用戶及訂單列表 -->
<resultMap id="UserWithOrdersMap" type="User">
<!-- 用戶表字段 -->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<!-- 一對多關(guān)聯(lián):collection映射訂單列表 -->
<collection property="orders" ofType="Order"> <!-- ofType指定集合元素類型 -->
<id column="order_id" property="id"/> <!-- 訂單ID(別名避免沖突) -->
<result column="order_no" property="orderNo"/>
<result column="total_amount" property="totalAmount"/>
<result column="user_id" property="userId"/>
</collection>
</resultMap>
<!-- 連接查詢:用戶與訂單 -->
<select id="selectUserWithOrdersById" resultMap="UserWithOrdersMap">
SELECT
u.id, u.username, u.age,
o.id AS order_id, o.order_no, o.total_amount, o.user_id
FROM user u
LEFT JOIN `order` o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
核心說明:
collection的property:對應(yīng)User類中的orders屬性(List類型);ofType:指定集合中元素的類型(Order),區(qū)別于javaType(用于指定屬性類型,如List);- 主表與關(guān)聯(lián)表的字段需通過別名區(qū)分(如
o.id AS order_id),避免映射混亂。
四、多對多關(guān)聯(lián)查詢
以“學(xué)生(student)與課程(course)”為例,一個學(xué)生可選多門課程,一門課程可有多個學(xué)生,通過中間表student_course實現(xiàn)關(guān)聯(lián)。
4.1 數(shù)據(jù)庫表設(shè)計
-- 學(xué)生表 CREATE TABLE `student` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 課程表 CREATE TABLE `course` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 中間表(多對多關(guān)聯(lián)) CREATE TABLE `student_course` ( `id` int NOT NULL AUTO_INCREMENT, `student_id` int NOT NULL, `course_id` int NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_stu_course` (`student_id`,`course_id`) -- 避免重復(fù)關(guān)聯(lián) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 實體類設(shè)計
// 學(xué)生類(包含課程列表)
@Data
public class Student {
private Integer id;
private String name;
// 多對多關(guān)聯(lián):學(xué)生包含多個課程
private List<Course> courses;
}
// 課程類
@Data
public class Course {
private Integer id;
private String name;
}
4.3 多對多查詢實現(xiàn)
多對多查詢本質(zhì)是一對多的擴(kuò)展(通過中間表連接),以連接查詢?yōu)槔?/p>
Mapper接口:
// 查詢學(xué)生及所選課程 Student selectStudentWithCoursesById(Integer id);
Mapper XML:
<resultMap id="StudentWithCoursesMap" type="Student">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- 多對多:collection映射課程列表 -->
<collection property="courses" ofType="Course">
<id column="course_id" property="id"/>
<result column="course_name" property="name"/>
</collection>
</resultMap>
<select id="selectStudentWithCoursesById" resultMap="StudentWithCoursesMap">
SELECT
s.id, s.name,
c.id AS course_id, c.name AS course_name
FROM student s
LEFT JOIN student_course sc ON s.id = sc.student_id
LEFT JOIN course c ON sc.course_id = c.id
WHERE s.id = #{id}
</select>
核心說明:
- 多對多通過“主表→中間表→關(guān)聯(lián)表”的
JOIN實現(xiàn); collection標(biāo)簽用法與一對多相同(均映射集合),區(qū)別在于表連接邏輯。
五、關(guān)聯(lián)查詢的優(yōu)化與最佳實踐
5.1 避免N+1查詢問題
N+1問題:嵌套查詢時,若查詢N個主表記錄,會觸發(fā)1次主表查詢+N次關(guān)聯(lián)表查詢,導(dǎo)致性能下降。
示例:查詢所有用戶及其訂單(嵌套查詢方式):
<!-- 1次主表查詢:查詢所有用戶 -->
<select id="selectAllUser" resultMap="UserWithOrdersNestedMap">
SELECT id, username, age FROM user
</select>
<!-- 每個用戶觸發(fā)1次訂單查詢(若有100個用戶,觸發(fā)100次) -->
<collection property="orders" select="selectOrdersByUserId" column="id"/>
解決方案:
- 優(yōu)先使用連接查詢(單輪查詢,無N+1問題);
- 若需嵌套查詢,開啟MyBatis二級緩存,緩存關(guān)聯(lián)查詢結(jié)果;
- 限制查詢數(shù)量(如分頁查詢),減少關(guān)聯(lián)查詢次數(shù)。
5.2 合理使用別名避免字段沖突
多表查詢時,若主表與關(guān)聯(lián)表有同名字段(如id、name),需通過別名區(qū)分,否則映射結(jié)果會被覆蓋。
-- 錯誤:未用別名,o.id會覆蓋u.id
SELECT u.id, u.name, o.id, o.name
FROM user u JOIN order o ON u.id = o.user_id
-- 正確:用別名區(qū)分
SELECT
u.id AS user_id, u.name AS user_name,
o.id AS order_id, o.name AS order_name
5.3 按需查詢關(guān)聯(lián)數(shù)據(jù)
并非所有場景都需要查詢關(guān)聯(lián)數(shù)據(jù)(如列表頁展示用戶基本信息,無需查詢訂單),應(yīng)根據(jù)場景設(shè)計不同查詢:
- 簡單查詢:僅查詢主表數(shù)據(jù)(無關(guān)聯(lián));
- 詳情查詢:查詢主表+關(guān)聯(lián)數(shù)據(jù)(通過連接查詢)。
5.4 延遲加載(按需加載關(guān)聯(lián)數(shù)據(jù))
MyBatis支持延遲加載(懶加載):查詢主表數(shù)據(jù)時不加載關(guān)聯(lián)數(shù)據(jù),僅當(dāng)訪問關(guān)聯(lián)屬性時才觸發(fā)關(guān)聯(lián)查詢,適合“大部分場景不需要關(guān)聯(lián)數(shù)據(jù)”的場景。
開啟延遲加載(在MyBatis配置文件中):
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!-- 全局開啟延遲加載 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 按需加載(訪問時才加載) -->
</settings>
使用場景:詳情頁默認(rèn)展示用戶信息,點擊“查看訂單”按鈕才加載訂單數(shù)據(jù)(通過代碼觸發(fā)關(guān)聯(lián)屬性訪問)。
六、常見問題與避坑指南
6.1 關(guān)聯(lián)對象為null(映射失?。?/h3>
問題:關(guān)聯(lián)對象(如idCard)為null,但數(shù)據(jù)庫存在關(guān)聯(lián)數(shù)據(jù)。
原因:
resultMap中column與SQL查詢的字段名不匹配(如SQL用card_id,resultMap寫column="id");- 表連接條件錯誤(如
JOIN條件不正確,導(dǎo)致關(guān)聯(lián)數(shù)據(jù)未查詢到); - 關(guān)聯(lián)表無匹配數(shù)據(jù)(正常情況,如用戶未綁定身份證,
idCard為null)。
解決方案:
- 檢查
resultMap的column是否與SQL查詢的字段(含別名)一致; - 單獨(dú)執(zhí)行SQL,確認(rèn)關(guān)聯(lián)數(shù)據(jù)是否被正確查詢;
- 若允許關(guān)聯(lián)數(shù)據(jù)為
null,無需處理(正常邏輯)。
6.2 集合數(shù)據(jù)重復(fù)(一條數(shù)據(jù)被多次映射)
問題:collection映射的列表中,同一條數(shù)據(jù)被重復(fù)添加(如一個訂單出現(xiàn)多次)。
原因:
- 未正確配置
id標(biāo)簽:resultMap中未用id標(biāo)簽指定關(guān)聯(lián)對象的唯一標(biāo)識(如訂單的id),MyBatis無法判斷數(shù)據(jù)是否重復(fù); - SQL查詢返回重復(fù)數(shù)據(jù)(如
JOIN導(dǎo)致主表數(shù)據(jù)被關(guān)聯(lián)表數(shù)據(jù)重復(fù))。
解決方案:
- 為關(guān)聯(lián)對象配置
id標(biāo)簽(collection內(nèi)的id),指定唯一標(biāo)識字段:
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/> <!-- 關(guān)鍵:指定訂單唯一標(biāo)識 -->
<!-- 其他字段 -->
</collection>
- 優(yōu)化SQL,避免返回重復(fù)數(shù)據(jù)(如使用
DISTINCT或調(diào)整JOIN邏輯)。
6.3 嵌套查詢參數(shù)傳遞錯誤
問題:嵌套查詢時,column傳遞的參數(shù)不正確,導(dǎo)致關(guān)聯(lián)查詢無結(jié)果。
解決方案:
- 確保
column的值與主查詢返回的字段名一致:
<!-- 主查詢返回字段為user_id -->
<select id="selectUser" resultMap="UserMap">
SELECT id AS user_id, username FROM user
</select>
<!-- 嵌套查詢需用主查詢的字段名作為參數(shù) -->
<association select="selectOrder" column="user_id"/> <!-- 正確:column="user_id" -->
- 傳遞多個參數(shù)時,用
column="{key1=col1, key2=col2}":
<association select="selectByParams" column="{id=user_id, name=user_name}"/>
總結(jié):關(guān)聯(lián)查詢的核心要點
MyBatis關(guān)聯(lián)查詢通過resultMap的association和collection標(biāo)簽,實現(xiàn)了多表數(shù)據(jù)到Java對象關(guān)聯(lián)關(guān)系的映射,核心要點如下:
- 標(biāo)簽選擇:
- 一對一用
association(映射單個對象); - 一對多/多對多用
collection(映射集合)。
- 查詢方式:
- 優(yōu)先用連接查詢(單輪
JOIN查詢,無N+1問題,性能好); - 嵌套查詢僅用于“關(guān)聯(lián)數(shù)據(jù)按需加載”場景(需注意N+1問題)。
- 優(yōu)化技巧:
- 用別名區(qū)分同名字段,避免映射沖突;
- 配置
id標(biāo)簽確保關(guān)聯(lián)數(shù)據(jù)不重復(fù); - 避免查詢無關(guān)字段,減少數(shù)據(jù)傳輸量。
到此這篇關(guān)于MyBatis的關(guān)聯(lián)查詢實現(xiàn)(一對一、一對多、多對多)的文章就介紹到這了,更多相關(guān)MyBatis 關(guān)聯(lián)查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 關(guān)于QueryWrapper,實現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式
- Mybatis多表關(guān)聯(lián)查詢的實現(xiàn)(DEMO)
- Mybatis 一對多和多對一關(guān)聯(lián)查詢問題
- mybatis-plus多表關(guān)聯(lián)查詢功能的實現(xiàn)
- mybatis如何使用注解實現(xiàn)一對多關(guān)聯(lián)查詢
- MyBatis實踐之動態(tài)SQL及關(guān)聯(lián)查詢
- MyBatis 三表外關(guān)聯(lián)查詢的實現(xiàn)(用戶、角色、權(quán)限)
- Mybatis關(guān)聯(lián)查詢之一對多和多對一XML配置詳解
- Mybatis實現(xiàn)一對一、一對多關(guān)聯(lián)查詢的方法(示例詳解)
- Mybatis-Plus多表關(guān)聯(lián)查詢的使用案例解析
相關(guān)文章
mybatisplus 的SQL攔截器實現(xiàn)關(guān)聯(lián)查詢功能
大家都知道m(xù)ybatisplus不支持關(guān)聯(lián)查詢,后來學(xué)習(xí)研究發(fā)現(xiàn)mybatisplus的SQL攔截器可以實現(xiàn)這一操作,下面小編給大家分享我的demo實現(xiàn)基本的關(guān)聯(lián)查詢功能沒有問題,對mybatisplus關(guān)聯(lián)查詢相關(guān)知識感興趣的朋友一起看看吧2021-06-06
java使用淘寶API讀寫json實現(xiàn)手機(jī)歸屬地查詢功能代碼
本文介紹java使用淘寶API讀寫json實現(xiàn)手機(jī)歸屬地查詢功能,代碼簡單,大家可以參考使用2013-11-11
Java+WebSocket實現(xiàn)簡單實時雙人協(xié)同pk答題系統(tǒng)
在實時互動應(yīng)用中,實現(xiàn)流暢的多人協(xié)同對戰(zhàn)功能是一大挑戰(zhàn),WebSocket技術(shù),以其全雙工通信能力,提供了解決方案,本文我們就來使用WebSocket實現(xiàn)簡單實時雙人協(xié)同pk答題系統(tǒng)吧2025-06-06

