「Mybatis系列」Mybatis高级应用

1. 关联查询

举例:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发,关联查询用户信息为一对一查询。如果从用户信息出发,查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。

1.1 一对一查询

需求

查询所有订单信息,关联查询下单用户信息。

SQL语句

主信息:订单表

从信息:用户表

SELECT 

  orders.*,

  user.username,

  user.address

FROM

  orders LEFT JOIN user 

  ON orders.user_id = user.id

方法一:resultType

返回resultType方式比较简单,也比较常用,就不做介绍了。

方法二:resultMap

使用resultMap进行结果映射,定义专门的resultMap用于映射一对一查询结果。

创建扩展po类

创建OrdersExt类(该类用于结果集封装),加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。

public classOrdersExtextendsOrders{

    private User user;// 用户对象

    // get/set。。。。

}

Mapper映射文件

在UserMapper.xml中,添加以下代码:

<!-- 查询订单关联用户信息使用resultmap -->

<!-- 一对一关联映射 -->

property:Orders对象的user属性

javaType:user属性对应 的类型

-->

<!-- column:user表的主键对应的列  property:user对象中id属性-->

SELECT

o.id,

o.user_id,

o.number,

o.createtime,

o.note,

u.username,

u.address

FROM

orders o

JOIN `user` u ON u.id = o.user_id

association:表示进行一对一关联查询映射

property:表示关联查询的结果存储在com.kkb.mybatis.po.Orders的user属性中

javaType:表示关联查询的映射结果类型

Mapper接口

在UserMapper接口中,添加以下接口方法:

publicListfindOrdersAndUserRstMap()throwsException;

测试代码

在UserMapperTest测试类中,添加测试代码:

publicvoidtestfindOrdersAndUserRstMap()throwsException{

//获取session

SqlSession session = sqlSessionFactory.openSession();

//获限mapper接口实例

UserMapper userMapper = session.getMapper(UserMapper.class);

//查询订单信息

List list = userMapper.findOrdersAndUserRstMap();

System.out.println(list);

//关闭session

session.close();

}

小结

使用resultMap进行结果映射时,具体是使用association完成关联查询的映射,将关联查询信息映射到pojo对象中。

1.2 一对多查询

需求

查询所有用户信息及用户关联的订单信息。

SQL语句

主信息:用户信息

从信息:订单信息

SELECT

    u.*, 

    o.id oid,

    o.number,

    o.createtime,

    o.note

FROM

    `user` u

LEFT JOIN orders o ON u.id = o.user_id

分析

在一对多关联查询时,只能使用resultMap进行结果映射:

1、一对多关联查询时,sql查询结果有多条,而映射对象是一个。

2、resultType完成结果映射的方式的一条记录映射一个对象。

3、resultMap完成结果映射的方式是以[主信息]为主对象,[从信息]映射为集合或者对象,然后封装到主对象中。

修改po类

在User类中加入List orders属性。

Mapper映射文件

在UserMapper.xml文件中,添加以下代码:

<!-- 用户信息映射 -->

<!-- 一对多关联映射 -->

SELECT

u.*,

o.id oid,

o.number,

o.createtime,

o.note

FROM

`user` u

LEFT JOIN orders o ON u.id = o.user_id

Collection标签:定义了一对多关联的结果映射。

property="orders":关联查询的结果集存储在User对象的上哪个属性。

ofType="orders":指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。

Mapper接口

// resultMap入门

publicListfindUserAndOrdersRstMap()throwsException;

测试代码

@Test

publicvoidtestFindUserAndOrdersRstMap(){

SqlSession session = sqlSessionFactory.openSession();

UserMapper userMapper = session.getMapper(UserMapper.class);

List result = userMapper.findUserAndOrdersRstMap();

for(User user : result) {

System.out.println(user);

}

session.close();

}

2. 延迟加载

2.1 什么是延迟加载

MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。

Mybatis的延迟加载,需要通过resultMap标签中的association和collection子标签才能演示成功。

Mybatis的延迟加载,也被称为是嵌套查询,对应的还有嵌套结果的概念,可以参考一对多关联的案例。

注意:MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的sql

2.2 延迟加载的分类

MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式加载与深度延迟加载

直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。

侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的某个属性(该属性不是关联对象的属性)时,就会马上执行关联对象的select查询。

深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。

延迟加载策略需要在Mybatis的全局配置文件中,通过标签进行设置。

2.3 案例准备

查询订单信息及它的下单用户信息。

2.4 直接加载

通过对全局参数:lazyLoadingEnabled进行设置,默认就是false。

<settings>    

<!-- 延迟加载总开关 -->   

 <setting name="lazyLoadingEnabled" value="false"/></settings>

    2.5 侵入式延迟加载

<settings>  

  <!-- 延迟加载总开关 -->  

  <setting name="lazyLoadingEnabled" value="true"/>  

  <!-- 侵入式延迟加载开关 -->   

 <setting name="aggressiveLazyLoading" value="true"/></settings>

2.6 深度延迟加载

<settings>   

 <!-- 延迟加载总开关 -->  

  <setting name="lazyLoadingEnabled" value="true"/>  

  <!-- 侵入式延迟加载开关 -->   

 <setting name="aggressiveLazyLoading" value="false"/></settings>

2.7 N+1问题

深度延迟加载的使用会提升性能。

如果延迟加载的表数据太多,此时会产生N+1问题,主信息加载一次算1次,而从信息是会根据主信息传递过来的条件,去查询从表多次。

3. 动态SQL

动态SQL的思想:就是使用不同的动态SQL标签去完成字符串的拼接处理、循环判断。

解决的问题是:

在映射文件中,会编写很多有重叠部分的SQL语句,比如SELECT语句和WHERE语句等这些重叠语句,该如何处理

SQL语句中的where条件有多个,但是页面只传递过来一个条件参数,此时会发生问题。

3.1 if标签

综合查询的案例中,查询条件是由页面传入,页面中的查询条件可能输入用户名称,也可能不输入用户名称。

 <select id="findUserList" parameterType="queryVo" resultType="user">     

   SELECT * FROM user where 1=1     

   <if test="user != null">       

    <if test="user.username != null and user.username != ''">          

      AND username like '%${user.username}%'      

      </if>    

    </if>  

  </select>

注意:要做『不等于空』字符串校验。

3.2 where标签

上边的sql中的1=1,虽然可以保证sql语句的完整性:但是存在性能问题。Mybatis提供where标签解决该问题。

代码修改如下:

 <select id="findUserList" parameterType="queryVo" resultType="user">   

     SELECT * FROM user     

   <!-- where标签会处理它后面的第一个and -->      

  <where>       

   <if test="user != null">     

           <if test="user.username != null and user.username != ''">       

             AND username like '%${user.username}%'           

     </if>       

     </if>    

    </where>   

 </select>

3.3 sql片段

在映射文件中可使用sql标签将重复的sql提取出来,然后使用include标签引用即可,最终达到sql重用的目的,具体实现如下:

原映射文件中的代码:

<select id="findUserList" parameterType="queryVo" resultType="user">  

    SELECT * FROM user      <!-- where标签会处理它后面的第一个and -->   

   <where>     

     <if test="user != null">      

        <if test="user.username != null and user.username != ''">        

          AND username like '%${user.username}%'        

      </if>       

   </if>            

     </where> 

 </select>

将where条件抽取出来:

<sql id="query_user_where">     

   <if test="user != null">      

      <if test="user.username != null and user.username != ''">           

     AND username like '%${user.username}%'     

       </if>     

   </if>

</sql>

使用include引用:

<!-- 使用包装类型查询用户 使用ognl从对象中取属性值,如果是包装对象可以使用.操作符来取内容部的属性 -->   

 <select id="findUserList" parameterType="queryVo" resultType="user">   

     SELECT * FROM user     

   <!-- where标签会处理它后面的第一个and -->  

      <where>      

      <include refid="query_user_where"></include>    

    </where>   

 </select>

注意:

1、如果引用其它mapper.xml的sql片段,则在引用时需要加上namespace,如下:

<include refid="namespace.sql片段”/>

3.4 foreach

需求

综合查询时,传入多个id查询用户信息,用下边两个sql实现:

SELECT * FROM USER WHERE username LIKE '%老郭%' AND (id =1 OR id =10 OR id=16)SELECT * FROM USER WHERE username LIKE '%老郭%'  AND  id  IN (1,10,16)

POJO

在pojo中定义list属性ids存储多个用户id,并添加getter/setter方法

Mapper映射文件

<sql id="query_user_where">     

   <if test="user != null">      

      <if test="user.username != null and user.username != ''">     

          AND username like '%${user.username}%'      

      </if>   

     </if>    

    <if test="ids != null and ids.size() > 0">     

       <!-- collection:指定输入的集合参数的参数名称 -->    

        <!-- item:声明集合参数中的元素变量名 -->        

    <!-- open:集合遍历时,需要拼接到遍历sql语句的前面 -->  

          <!-- close:集合遍历时,需要拼接到遍历sql语句的后面 -->       

     <!-- separator:集合遍历时,需要拼接到遍历sql语句之间的分隔符号 -->          

  <foreach collection="ids" item="id" open=" AND id IN ( "      

          close=" ) " separator=",">      

          #{id}      

      </foreach>   

     </if>  

  </sql

测试代码

在UserMapperTest测试代码中,修改testFindUserList方法,如下:

@Test

publicvoidtestFindUserList()throwsException{

SqlSession sqlSession = sqlSessionFactory.openSession();

// 获得mapper的代理对象

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 创建QueryVo对象

QueryVo queryVo =newQueryVo();

// 创建user对象

User user =newUser();

user.setUsername("老郭");

queryVo.setUser(user);

List ids =newArrayList();

ids.add(1);// 查询id为1的用户

ids.add(10);// 查询id为10的用户

queryVo.setIds(ids);

// 根据queryvo查询用户

List list = userMapper.findUserList(queryVo);

System.out.println(list);

sqlSession.close();

}

注意事项

如果parameterType不是POJO类型,而是List或者Array的话,那么foreach语句中,collection属性值需要固定写死为list或者array。

以上内容都是我自己的一些感想,分享出来欢迎大家指正,顺便求一波关注

作者:双哥

出处:https://juejin.im/post/5e4f33526fb9a07c9645920e

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容