深入解析MybatisPlus多表連接查詢
一、序言
在開(kāi)發(fā)過(guò)程中,數(shù)據(jù)庫(kù)查詢是非常常見(jiàn)的操作。而在一些復(fù)雜的業(yè)務(wù)場(chǎng)景中,我們經(jīng)常會(huì)遇到多表連接查詢的需求。針對(duì)這種情況,MybatisPlus提供了一系列強(qiáng)大的功能來(lái)支持一對(duì)一查詢、一對(duì)多查詢和多對(duì)多查詢。無(wú)論是查詢單條記錄還是查詢多條記錄,MybatisPlus都能提供靈活的解決方案。
通過(guò)一對(duì)一查詢,我們可以方便地從主表中獲取關(guān)聯(lián)表的詳細(xì)信息;一對(duì)多查詢能夠幫助我們從一個(gè)主表中獲取多個(gè)關(guān)聯(lián)表的記錄;而多對(duì)多查詢則可以輕松地獲取多個(gè)表之間的交叉組合。在實(shí)現(xiàn)這些查詢時(shí),MybatisPlus提供了豐富的注解和接口來(lái)簡(jiǎn)化我們的開(kāi)發(fā)工作。通過(guò)合理的配置,我們可以高效地完成各種類型的多表連接查詢操作,提升應(yīng)用程序的性能和開(kāi)發(fā)效率。
?接下來(lái)的文章中,我們將深入探討以上查詢類型的具體實(shí)現(xiàn)方法,以及一些常見(jiàn)的注意事項(xiàng)。通過(guò)學(xué)習(xí)和理解這些內(nèi)容,相信您能對(duì)MybatisPlus多表連接查詢有更深入的了解,為您的項(xiàng)目開(kāi)發(fā)提供有力的支持。
1. 組件及版本說(shuō)明
- SpringBoot 2.6.x
- MyBatisPlus 3.5.x
- Mysql 5.7.x
2. 場(chǎng)景
為了說(shuō)明連接查詢的關(guān)系,這里以學(xué)生、課程及其關(guān)系為示例。

3. 前期準(zhǔn)備
此部分需要讀者掌握以下內(nèi)容:Lambda 表達(dá)式、特別是方法引用;函數(shù)式接口;流式運(yùn)算等等,否則理解起來(lái)會(huì)有些吃力。實(shí)體類與 Vo 的映射關(guān)系,作者創(chuàng)造性的引入特別構(gòu)造器,合理利用繼承關(guān)系,極大的方便了開(kāi)發(fā)者完成實(shí)體類向 Vo 的轉(zhuǎn)換。空指針異常忽略不處理,借助Java8 新特性的Optional類實(shí)現(xiàn)。

二、一對(duì)一查詢
一對(duì)一查詢最典型的應(yīng)用場(chǎng)景是將id替換成name,比如將userId替換成userName。
(一)查詢單條記錄
查詢單條記錄是指返回值僅有一條記錄,通常是以唯一索引作為條件的返回查詢結(jié)果。
1、示例代碼
/**
* 查詢單個(gè)學(xué)生信息(一個(gè)學(xué)生對(duì)應(yīng)一個(gè)部門(mén))
*/
public UserVo getOneUser(Integer userId) {
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getUserId, userId);
// 先查詢用戶信息
User user = userMapper.selectOne(wrapper);
// 轉(zhuǎn)化為Vo
UserVo userVo = Optional.ofNullable(user).map(UserVo::new).orElse(null);
// 從其它表查詢信息再封裝到Vo
Optional.ofNullable(userVo).ifPresent(this::addDetpNameInfo);
return userVo;
}
附屬函數(shù)addDetpNameInfo代碼補(bǔ)充
/**
* 補(bǔ)充部門(mén)名稱信息
*/
private void addDetpNameInfo(UserVo userVo) {
LambdaQueryWrapper<Dept> wrapper = Wrappers.lambdaQuery(Dept.class)
.eq(Dept::getDeptId, userVo.getDeptId());
Dept dept = deptMapper.selectOne(wrapper);
Optional.ofNullable(dept).ifPresent(e -> userVo.setDeptName(e.getDeptName()));
}
2、理論分析
查詢單個(gè)實(shí)體共分為兩個(gè)步驟:根據(jù)條件查詢主表數(shù)據(jù)(需處理空指針異常);封裝 Vo 并查詢附屬表數(shù)據(jù)。
查詢結(jié)果(VO)只有一條記錄,需要查詢兩次數(shù)據(jù)庫(kù),時(shí)間復(fù)雜度為O(1)。
(二)查詢多條記錄
查詢多條記錄是指查詢結(jié)果為列表,通常是指以普通索引為條件的查詢結(jié)果。
1、示例代碼
/**
* 批量查詢學(xué)生信息(一個(gè)學(xué)生對(duì)應(yīng)一個(gè)部門(mén))
*/
public List<UserVo> getUserByList() {
// 先查詢用戶信息(表現(xiàn)形式為列表)
List<User> user = userMapper.selectList(Wrappers.emptyWrapper());
List<UserVo> userVos = user.stream().map(UserVo::new).collect(toList());
// 此步驟可以有多個(gè)
addDeptNameInfo(userVos);
return userVos;
}
附屬函數(shù)addDeptNameInfo代碼補(bǔ)充
private void addDeptNameInfo(List<UserVo> userVos) {
// 提取用戶userId,方便批量查詢
Set<Integer> deptIds = userVos.stream().map(User::getDeptId).collect(toSet());
// 根據(jù)deptId查詢deptName(查詢前,先做非空判斷)
List<Dept> dept = deptMapper.selectList(Wrappers.lambdaQuery(Dept.class).in(Dept::getDeptId, deptIds));
// 構(gòu)造映射關(guān)系,方便匹配deptId與deptName
Map<Integer, String> hashMap = dept.stream().collect(toMap(Dept::getDeptId, Dept::getDeptName));
// 封裝Vo,并添加到集合中(關(guān)鍵內(nèi)容)
userVos.forEach(e -> e.setDeptName(hashMap.get(e.getDeptId())));
}
2、理論分析
先查詢包含id的列表記錄,從結(jié)果集中析出id并轉(zhuǎn)化成批查詢語(yǔ)句再訪問(wèn)數(shù)據(jù)庫(kù),從第二次調(diào)用結(jié)果集中解析出name。
查詢結(jié)果(VO)有多條記錄,但僅調(diào)用兩次數(shù)據(jù)庫(kù),時(shí)間復(fù)雜度為O(1)。
三、一對(duì)多查詢
一對(duì)多查詢最常見(jiàn)的場(chǎng)景是查詢部門(mén)所包含的學(xué)生信息,由于一個(gè)部門(mén)對(duì)應(yīng)多個(gè)學(xué)生,每個(gè)學(xué)生對(duì)應(yīng)一個(gè)部門(mén),因此稱為一對(duì)多查詢。
(一)查詢單條記錄
1、示例代碼
/**
* 查詢單個(gè)部門(mén)(其中一個(gè)部門(mén)有多個(gè)用戶)
*/
public DeptVo getOneDept(Integer deptId) {
// 查詢部門(mén)基礎(chǔ)信息
LambdaQueryWrapper<Dept> wrapper = Wrappers.lambdaQuery(Dept.class).eq(Dept::getDeptId, deptId);
DeptVo deptVo = Optional.ofNullable(deptMapper.selectOne(wrapper)).map(DeptVo::new).orElse(null);
Optional.ofNullable(deptVo).ifPresent(this::addUserInfo);
return deptVo;
}
附屬函數(shù)addUserInfo代碼補(bǔ)充
private void addUserInfo(DeptVo deptVo) {
// 根據(jù)部門(mén)deptId查詢學(xué)生列表
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).eq(User::getDeptId, deptVo.getDeptId());
List<User> users = userMapper.selectList(wrapper);
deptVo.setUsers(users);
}
2、理論分析
整個(gè)過(guò)程共分為兩個(gè)階段:通過(guò)部門(mén)表中主鍵查詢指定部門(mén)信息,通過(guò)學(xué)生表中部門(mén)ID外鍵查詢學(xué)生信息,將結(jié)果合并,形成返回值(Vo)。
一對(duì)多查詢單條記錄整個(gè)過(guò)程至多需要調(diào)用2次數(shù)據(jù)庫(kù)查詢,查詢次數(shù)為常數(shù),查詢時(shí)間復(fù)雜度為O(1)。
(二)查詢多條記錄
1、示例代碼
/**
* 查詢多個(gè)部門(mén)(其中一個(gè)部門(mén)有多個(gè)用戶)
*/
public List<DeptVo> getDeptByList() {
// 按條件查詢部門(mén)信息
List<Dept> deptList = deptMapper.selectList(Wrappers.emptyWrapper());
List<DeptVo> deptVos = deptList.stream().map(DeptVo::new).collect(toList());
if (deptVos.size() > 0) {
addUserInfo(deptVos);
}
return deptVos;
}
附屬函數(shù)addUserInfo代碼補(bǔ)充
private void addUserInfo(List<DeptVo> deptVos) {
// 準(zhǔn)備deptId方便批量查詢用戶信息
Set<Integer> deptIds = deptVos.stream().map(Dept::getDeptId).collect(toSet());
// 用批量deptId查詢用戶信息
List<User> users = userMapper.selectList(Wrappers.lambdaQuery(User.class).in(User::getDeptId, deptIds));
// 重點(diǎn):將用戶按照deptId分組
Map<Integer, List<User>> hashMap = users.stream().collect(groupingBy(User::getDeptId));
// 合并結(jié)果,構(gòu)造Vo,添加集合列表
deptVos.forEach(e -> e.setUsers(hashMap.get(e.getDeptId())));
}
2、理論分析
整個(gè)過(guò)程共分為三個(gè)階段:通過(guò)普通索引從部門(mén)表中查詢?nèi)舾蓷l記錄;將部門(mén)ID轉(zhuǎn)化為批查詢從學(xué)生表中查詢學(xué)生記錄;將學(xué)生記錄以部門(mén)ID為單位進(jìn)行分組,合并結(jié)果,轉(zhuǎn)化為Vo。
一對(duì)多查詢多條記錄需要調(diào)用2次數(shù)據(jù)庫(kù)查詢,查詢次數(shù)為常數(shù),查詢時(shí)間復(fù)雜度為O(1)。
四、多對(duì)多查詢
MybatisPlus 實(shí)現(xiàn)多對(duì)多查詢是一件極富挑戰(zhàn)性的任務(wù),也是連接查詢中最困難的部分。
以空間置換時(shí)間,借助于流式運(yùn)算,解決多對(duì)多查詢難題。
多對(duì)多查詢相對(duì)于一對(duì)多查詢,增加了流式分組運(yùn)算、批量 HashMap 取值等內(nèi)容。

(一)查詢單條記錄
查詢單條記錄一般是指通過(guò)兩個(gè)查詢條件查詢出一條匹配表中的記錄。
1、示例代碼
public StudentVo getStudent(Integer stuId) {
// 通過(guò)主鍵查詢學(xué)生信息
StudentVo studentVo = ConvertUtils.convertObj(getById(stuId), StudentVo::new);
LambdaQueryWrapper<StuSubRelation> wrapper = Wrappers.lambdaQuery(StuSubRelation.class).eq(StuSubRelation::getStuId, stuId);
// 查詢匹配關(guān)系
List<StuSubRelation> stuSubRelations = stuSubRelationMapper.selectList(wrapper);
Set<Integer> subIds = stuSubRelations.stream().map(StuSubRelation::getSubId).collect(toSet());
if (studentVo != null && subIds.size() > 0) {
List<Subject> subList = subjectMapper.selectList(Wrappers.lambdaQuery(Subject.class).in(Subject::getId, subIds));
List<SubjectBo> subBoList = EntityUtils.toList(subList, SubjectBo::new);
HashBasedTable<Integer, Integer, Integer> table = getHashBasedTable(stuSubRelations);
subBoList.forEach(e -> e.setScore(table.get(stuId, e.getId())));
studentVo.setSubList(subBoList);
}
return studentVo;
}
2、理論分析
多對(duì)多單條記錄查詢最多訪問(wèn)數(shù)據(jù)庫(kù)3次,先查詢學(xué)生信息,然后查詢學(xué)生與課程匹配信息,最后查詢課程分?jǐn)?shù)信息,查詢時(shí)間復(fù)雜度為O(1)。
(二)查詢多條記錄
1、示例代碼
public List<StudentVo> getStudentList() {
// 通過(guò)主鍵查詢學(xué)生信息
List<StudentVo> studentVoList = EntityUtils.toList(list(), StudentVo::new);
// 批量查詢學(xué)生ID
Set<Integer> stuIds = studentVoList.stream().map(Student::getId).collect(toSet());
LambdaQueryWrapper<StuSubRelation> wrapper = Wrappers.lambdaQuery(StuSubRelation.class).in(StuSubRelation::getStuId, stuIds);
List<StuSubRelation> stuSubRelations = stuSubRelationMapper.selectList(wrapper);
// 批量查詢課程ID
Set<Integer> subIds = stuSubRelations.stream().map(StuSubRelation::getSubId).collect(toSet());
if (stuIds.size() > 0 && subIds.size() > 0) {
HashBasedTable<Integer, Integer, Integer> table = getHashBasedTable(stuSubRelations);
List<Subject> subList = subjectMapper.selectList(Wrappers.lambdaQuery(Subject.class).in(Subject::getId, subIds));
List<SubjectBo> subjectBoList = EntityUtils.toList(subList, SubjectBo::new);
Map<Integer, List<Integer>> map = stuSubRelations.stream().collect(groupingBy(StuSubRelation::getStuId, mapping(StuSubRelation::getSubId, toList())));
for (StudentVo studentVo : studentVoList) {
// 獲取課程列表
List<SubjectBo> list = ListUtils.select(subjectBoList, e -> emptyIfNull(map.get(studentVo.getId())).contains(e.getId()));
// 填充分?jǐn)?shù)
list.forEach(e -> e.setScore(table.get(studentVo.getId(), e.getId())));
studentVo.setSubList(list);
}
}
return studentVoList;
}
2、理論分析
多對(duì)多N條記錄查詢由于使用了批查詢,因此最多訪問(wèn)數(shù)據(jù)庫(kù)也是3次,先查詢學(xué)生信息,然后查詢學(xué)生與課程匹配信息,最后查詢課程分?jǐn)?shù)信息,查詢時(shí)間復(fù)雜度為O(1)。
五、總結(jié)和拓展
1、總結(jié)
通過(guò)上述分析,能夠用 MybatisPlus 解決多表連接查詢中的一對(duì)一、一對(duì)多、多對(duì)多查詢。
- 上述代碼行文緊湊,充分利用 IDE 對(duì) Lambda 表達(dá)式的支持,在編譯期間完成對(duì)代碼的檢查。
- 業(yè)務(wù)邏輯清晰,可維護(hù)性、可修改性優(yōu)勢(shì)明顯。
- 一次查詢需要訪問(wèn)至多兩次數(shù)據(jù)庫(kù),時(shí)間復(fù)雜度為
o(1),主鍵查詢或者索引查詢,查詢效率高。
2、拓展
MybatisPlus能很好的解決單表查詢問(wèn)題,同時(shí)借助在單表查詢的封裝能很好地解決連接查詢問(wèn)題。
本方案不僅解決了連接查詢問(wèn)題,同時(shí)具備如下內(nèi)容拓展:
當(dāng)數(shù)據(jù)量較大時(shí),仍然具有穩(wěn)定的查詢效率
當(dāng)數(shù)據(jù)量達(dá)到百萬(wàn)級(jí)別時(shí),傳統(tǒng)的單表通過(guò)索引查詢已經(jīng)面臨挑戰(zhàn),普通的多表連接查詢性能隨著數(shù)據(jù)量的遞增呈現(xiàn)指數(shù)級(jí)下降。
本方案通過(guò)將連接查詢轉(zhuǎn)化為主鍵(索引)查詢,查詢性能等效于單表查詢。
與二級(jí)緩存配合使用進(jìn)一步提高查詢效率
當(dāng)所有的查詢均轉(zhuǎn)化為以單表為基礎(chǔ)的查詢后,方能安全的引入二級(jí)緩存。二級(jí)緩存的單表增刪改查操作自適應(yīng)聯(lián)動(dòng),解決了二級(jí)緩存的臟數(shù)據(jù)問(wèn)題。
到此這篇關(guān)于深入解析MybatisPlus多表連接查詢的文章就介紹到這了,更多相關(guān)MybatisPlus多表連接查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Mybatis-Plus多表關(guān)聯(lián)查詢的使用案例解析
- MyBatis多表查詢和注解開(kāi)發(fā)案例詳解
- mybatis-plus多表分頁(yè)查詢最佳實(shí)現(xiàn)方法(非常簡(jiǎn)單)
- Mybatis-plus實(shí)現(xiàn)join連表查詢的示例代碼
- MyBatis中ResultMap與多表查詢的處理方法
- MybatisPlus多表連接查詢的具體實(shí)現(xiàn)
- mybatis于xml方式和注解方式實(shí)現(xiàn)多表查詢的操作方法
- mybatis-plus多表查詢操作方法
- MyBatis?實(shí)現(xiàn)動(dòng)態(tài)排序的多表查詢
- Mybatis分頁(yè)查詢主從表的實(shí)現(xiàn)示例
- mybatis連接數(shù)據(jù)庫(kù)實(shí)現(xiàn)雙表查詢
相關(guān)文章
Java 實(shí)現(xiàn)實(shí)時(shí)監(jiān)聽(tīng)文件夾是否有新文件增加并上傳服務(wù)器功能
本文中主要陳述一種實(shí)時(shí)監(jiān)聽(tīng)文件夾中是否有文件增加的功能,可用于實(shí)際文件上傳功能的開(kāi)發(fā)。本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2019-09-09
Java 重命名 Excel 工作表并設(shè)置工作表標(biāo)簽顏色的示例代碼
這篇文章主要介紹了Java 重命名 Excel 工作表并設(shè)置工作表標(biāo)簽顏色的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot定時(shí)任務(wù)詳解與案例代碼
SpringBoot是一個(gè)流行的Java開(kāi)發(fā)框架,它提供了許多便捷的特性來(lái)簡(jiǎn)化開(kāi)發(fā)過(guò)程,其中之一就是定時(shí)任務(wù)的支持,讓開(kāi)發(fā)人員可以輕松地在應(yīng)用程序中執(zhí)行定時(shí)任務(wù),本文將詳細(xì)介紹如何在Spring?Boot中使用定時(shí)任務(wù),并提供相關(guān)的代碼示例2023-06-06
使用Springboot整合GridFS實(shí)現(xiàn)文件操作
這篇文章主要介紹了使用Springboot整合GridFS實(shí)現(xiàn)文件操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
詳解Spring boot/Spring 統(tǒng)一錯(cuò)誤處理方案的使用
這篇文章主要介紹了詳解Spring boot/Spring 統(tǒng)一錯(cuò)誤處理方案的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Java中通過(guò)反射實(shí)現(xiàn)代理Proxy代碼實(shí)例
這篇文章主要介紹了Java中通過(guò)反射實(shí)現(xiàn)代理Proxy代碼實(shí)例,java實(shí)現(xiàn)代理可以通過(guò)java.lang.reflect.Proxy接口結(jié)合java.lang.reflect.InvocationHandler來(lái)實(shí)現(xiàn),需要的朋友可以參考下2023-08-08
IntelliJ IDEA基于Scala實(shí)現(xiàn)Git檢查工具
這篇文章主要介紹了如何使用Scala實(shí)現(xiàn)自定義的Git檢查工具,大家可以基于本文的示例進(jìn)行擴(kuò)展與實(shí)現(xiàn),也可以進(jìn)行其他應(yīng)用方向的嘗試,感興趣的可以了解下2023-08-08

