mybatis一对多关联查询_一对一,一对多,多对多查询及延迟加载(N+1问题)分析
推薦學(xué)習(xí)
- 重識(shí)SSM,“超高頻面試點(diǎn)+源碼解析+實(shí)戰(zhàn)PDF”,一次性干掉全拿走
- 全網(wǎng)獨(dú)家的“MySQL高級(jí)知識(shí)”集合,骨灰級(jí)收藏,手慢則無
- “吃”完這本Java性能調(diào)優(yōu)實(shí)戰(zhàn),MySQL+JVM+Tomcat等問題一鍵全消
前言
之前分析了MyBatis中的配置的使用,而MyBatis中動(dòng)態(tài)標(biāo)簽功能也非常強(qiáng)大,本文不會(huì)介紹全部標(biāo)簽,主要是針對(duì)resultMap來介紹復(fù)雜查詢?cè)撊绾卫胹ql標(biāo)簽來配置動(dòng)態(tài)sql。
固定參數(shù)的查詢
首先我們來看一個(gè)帶有固定參數(shù)的查詢語句該如何實(shí)現(xiàn):
UserMapper.java中新增如下兩個(gè)方法:
對(duì)應(yīng)UserMapper.xml中的sql語句為:
select user_id,user_name from lw_user where user_name=#{userName} select user_id,user_name from ${tableName}然后執(zhí)行查詢:
package com.lonelyWolf.mybatis;import com.alibaba.fastjson.JSONObject;import com.lonelyWolf.mybatis.mapper.UserMapper;import com.lonelyWolf.mybatis.model.LwUser;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;import java.util.List;public class MyBatisQueryByParam { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; //讀取mybatis-config配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //創(chuàng)建SqlSessionFactory對(duì)象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //創(chuàng)建SqlSession對(duì)象 SqlSession session = sqlSessionFactory.openSession(); /** * 相比較于session.selectList("com.xxx.UserMapper.listAllUser")來實(shí)現(xiàn)查詢, * 下面這種通過先獲取mapper再挑用mapper中方法的方式會(huì)更靈活 */ UserMapper userMapper = session.getMapper(UserMapper.class); List userList = userMapper.listUserByUserName("孤狼1號(hào)"); System.out.println(null == userList ? "": JSONObject.toJSONString(userList)); List userList2 = userMapper.listUserByTable("lw_user"); System.out.println(null == userList2 ? "": JSONObject.toJSONString(userList2)); }}查詢結(jié)果輸出如下:
#和$區(qū)別
從上面的輸出sql語句截圖可以看到,如果使用#的話,那么sql語句會(huì)先在sql語句中使用占位符,也就是預(yù)編譯,對(duì)應(yīng)JBDC中的PreparedStatement。而使用$,則會(huì)直接把參數(shù)拼到sql語句上,相當(dāng)于JDBC中的Statement。
一般情況下不建議使用$,因?yàn)檫@種直接拼接的方式容易被sql注入攻擊。
比如,上面的sql語句:
假如tableName傳入的是:lw_user;delete from lw_user;那么這時(shí)候執(zhí)行的sql語句就會(huì)變成:
select user_id,user_name from lw_user;delete from lw_user;這時(shí)候整張表的數(shù)據(jù)都會(huì)被刪除,而如果使用的是#{tableName},最終執(zhí)行的是如下sql:
select user_id,user_name from 'lw_user;delete from lw_user;'產(chǎn)生的后果只是查詢了一張不存在的表而已。
動(dòng)態(tài)參數(shù)的查詢
上面的例子中參數(shù)是固定的,那么假如我們參數(shù)不固定呢?比如有2個(gè)參數(shù),但是我可能一個(gè)都不用,也可能只用1個(gè),或者2個(gè)都用。這種又該如何實(shí)現(xiàn)呢?
如下圖所示,可以通過where和if標(biāo)簽結(jié)合使用,兩個(gè)條件都寫了and,這是因?yàn)镸ybatis會(huì)幫我們處理掉多余的and關(guān)鍵字。
或者說我們對(duì)同一個(gè)參數(shù)需要進(jìn)行不同取值拼接不同的sql,那么可以通過choose標(biāo)簽根據(jù)不同的參數(shù)拼接不同的sql
select user_id,user_name from lw_user and user_id=#{userId} and user_id=#{userId} and user_id=#{userId}當(dāng)然,Mybatis還提供了其他許多標(biāo)簽,用來處理更加復(fù)雜的組合,在這里就不舉例說明了。
一對(duì)一查詢
假如我們現(xiàn)在有兩種表,是一對(duì)一關(guān)系,我們想同時(shí)查詢出來,當(dāng)然最簡單的辦法是再寫一個(gè)類,把兩張表的結(jié)果屬性都放到一個(gè)類里面,但是這種方式無疑會(huì)造成了很多重復(fù)代碼,而且體現(xiàn)不出層級(jí)關(guān)系,假如我們有一張表lw_user表,存儲(chǔ)用戶信息,另一張表lw_user_job存儲(chǔ)了用戶的工作經(jīng)歷,那么很明顯,job對(duì)應(yīng)類應(yīng)該包含在user類內(nèi),這種應(yīng)該怎么實(shí)現(xiàn)呢?
請(qǐng)看!
1、新建一個(gè)實(shí)體類UserJob來映射lw_user_job表屬性:
package com.lonelyWolf.mybatis.model;public class LwUserJob { private String id; private String userId; //用戶id private String companyName; //公司名 private String position; //職位 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; }}2、在原先的LwUser類增加一個(gè)引用屬性來引用LwUserJob:
package com.lonelyWolf.mybatis.model;public class LwUser { private String userId; //用戶id private String userName; //用戶名稱 private LwUserJob usreJobInfo;//用戶工作信息 public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public LwUserJob getUsreJobInfo() { return usreJobInfo; } public void setUsreJobInfo(LwUserJob usreJobInfo) { this.usreJobInfo = usreJobInfo; }}3、UserMapper.java中新增一個(gè)方法:
List listUserAndJob();這時(shí)候UserMapper.xml需要自定義一個(gè)ResultMap:
select * from lw_user u inner join lw_user_job j on u.user_id=j.user_id這時(shí)候執(zhí)行查詢就可以得到如下結(jié)果:
[{"userId":"1","userJobInfo":{"companyName":"自由職業(yè)","id":"11","position":"初級(jí)開發(fā)"},"userName":"孤狼1號(hào)"}]一對(duì)多查詢
假設(shè)用戶信息和工作信息是1對(duì)多的關(guān)系,又該如何呢?
只需做2步簡單的改造:
1、將LwUser中引用屬性改為List:
2、Mapper中的ResultMap文件同時(shí)做出修改,通過collection標(biāo)簽代替association標(biāo)簽,同時(shí)javaType修改為ofType:
再次執(zhí)行查詢得到如下結(jié)果:
[{"userId":"1","userJobList":[{"companyName":"自由職業(yè)","id":"11","position":"初級(jí)開發(fā)"}],"userName":"孤狼1號(hào)"}]可以看到這時(shí)候的userJobList已經(jīng)是一個(gè)數(shù)組了。
PS:記得之前有人問過屬性的映射是不是必須把表里面所有的屬性都映射才行,答案是否定的,需要幾個(gè)就映射幾個(gè),不需要完全映射過來。
多對(duì)多查詢
多對(duì)多其實(shí)和一對(duì)多差不多的原理,都是利用collection標(biāo)簽,就是在collection標(biāo)簽里面再嵌套collection標(biāo)簽就可以實(shí)現(xiàn)多對(duì)多的查詢,在這里就不在舉例了。
延遲加載(解決N+1問題)
我們先來看一下一對(duì)多的另一種寫法,就是支持一種嵌套查詢:
select * from lw_user select * from lw_user_job where user_id=#{userId}上面的collection內(nèi)部并沒有定義屬性,但是collection上面定義了兩個(gè)標(biāo)簽,代表的含義是將當(dāng)前查詢結(jié)果的值user_id傳遞到查詢selectJob中去。我們定義方法來執(zhí)行一下這個(gè)外部查詢selectUserAndJob看看會(huì)有什么結(jié)果:
可以看到外部查詢有幾條數(shù)據(jù)就會(huì)觸發(fā)內(nèi)部查詢幾次,這就是嵌套查詢引發(fā)的N+1問題。(使用association標(biāo)簽也會(huì)存在這個(gè)問題)
這種在對(duì)性能要求比較高的場景中是不允許的,非常的浪費(fèi)資源,MyBatis官方也不建議我們使用這種方式。
解決N+1問題
MyBatis雖然不建議我們使用這種嵌套查詢,但是也提供了一種解決N+1問題的方式,那就是當(dāng)我們執(zhí)行外部查詢的時(shí)候不會(huì)觸發(fā)內(nèi)部查詢,僅僅當(dāng)我們使用到了內(nèi)部對(duì)象的時(shí)候才會(huì)觸發(fā)內(nèi)部查詢來獲取對(duì)應(yīng)結(jié)果,這種就叫延遲加載。
延遲加載需要通過全局屬性來控制,默認(rèn)是關(guān)閉的。
我們?cè)趍ybatis-config.xml文件中開啟延遲加載試試:
然后我們?cè)賮韴?zhí)行如下語句:
List userList = userMapper.selectUserAndJob(); System.out.println(userList.size());//不會(huì)觸發(fā) System.out.println(userList.get(0).getUserJobList());//觸發(fā) System.out.println(null == userList ? "": JSONObject.toJSONString(userList));//觸發(fā)輸出結(jié)果為:
延遲加載原理
延遲加載其實(shí)就是利用了動(dòng)態(tài)代理來生成一個(gè)新的對(duì)象,默認(rèn)用的是Javassist動(dòng)態(tài)代理,可以通過參數(shù)來控制,支持切換為CGLIB:
總結(jié)
本文主要講述了如何利用MyBatis實(shí)現(xiàn)一對(duì)一,一對(duì)多以及多對(duì)多的查詢,并且講解了如何利用延遲加載來解決嵌套查詢中的N+1問題。MyBatis系列中前兩篇相對(duì)基礎(chǔ),并沒有深入分析實(shí)現(xiàn)原理,僅僅只是講解了如何使用,下一篇開始,將會(huì)深入分析MyBatis的源碼以及一些高級(jí)特性比如sqlSession執(zhí)行流程,緩存,參數(shù)和結(jié)果集映射等功能的實(shí)現(xiàn)原理。
作者:雙子孤狼
原文鏈接:https://blog.csdn.net/zwx900102/article/details/108559220
總結(jié)
以上是生活随笔為你收集整理的mybatis一对多关联查询_一对一,一对多,多对多查询及延迟加载(N+1问题)分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python测试用例怎么写_Python
- 下一篇: 用ram实现寄存器堆_纯C语言实现boo