MyBatis动态SQL语法详解(二)

一、MyBatis查询返回

1.1、MyBatis查询返回对象

Dao接口:

public employee getEmpById(Integer id) ;

mapper映射SQL:

<select id="getEmpById"  resultType="com.xiaolang.employee">
	select * from employee where id=#{id}
</select>

1.2、MyBatis查询返回list集合

Dao接口:

public List<Employee> getEmpsByName(String name) ;

mapper映射SQL:

<select id="getEmpsByName"  resultType="com.xiaolang.employee">
	select * from employee where name like #{name}
</select>

1.3、MyBatis查询返回Map

Dao接口:(返回一条记录的map,key就是列明,值就是对应的值)

public Map<String, Object> getEmpsByIdReturnMap(Integer id) ;

mapper映射SQL:

<select id="getEmpsByIdReturnMap"  resultType="map">
	select * from employee where id=#{id}
</select>

多条记录封装成一个Map:要求:键是记录的主键id,值是employee对象。
Dao接口:(返回一条记录的map,key就是列明,值就是对应的值)

@MapKey("id")   //告诉Mybatis封装这个map的时候使用id作为map主键
public Map<Integer, Employee> getEmpsByNameReturnMap(String name) ;

mapper映射SQL:

<select id="getEmpsByNameReturnMap"  resultType="com.xiaolang.employee">
	select * from employee where name like #{name}
</select>

1.4、MyBatis查询自定义结果映射规则

<resultMap>是对数据库返回的结果集进行自定义映射,结果集过滤、控制等操作的动作标签。
  在MyBatis中,当SQL中的字段名与java中定义的bean对象字段名对不上的时候,可以直接在MyBatis全局映射文件中设置setting标签进行修改,从而实现字段映射匹配。
  今天我们在学习一种解决方式,通过自动义resultMap,实现高级结果集的映射。

resultMap自定义结果集映射规则,是对数据库返回的结果集进行自定义映射,数据过滤的操作。用于自定义javaBean的封装规则,方于mapper映射文件的<mapper>
属性:
  type:自定义规则的java类型
  id:唯一id,方便引用

Dao接口:

public employee getEmpById(Integer id) ;

mapper映射文件和SQL:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="Dao.IEmpDao" >
	<!--自定义resultMap:id为resultMap标识, type为bean类名或别名 -->
	<resultMap type="com.xiaolang.employee" id="MyEmp">
	  <!--指定主键列的封装规则
	  	  id:使用<id>标签来定义主键列,MyBatis底层有优化。
	  	  column:指定哪一列(oracle或mySSQL列)
	  	  property:指定对应的javaBean属性
	  -->
	  <id column="id" property="id" javaType="int" jdbcType="INTEGER"/>
	  
	  <!--指定封装last_name非主键列 -->
	  <result column="last_name" property="lastName" javaType="string" jdbcType="CHAR"/>
	  
	  <!--当然,其他未指定的列MyBatis会自动封装,但是更推荐全部映射规则都写上 -->
	  <result column="email" property="email" javaType="string" jdbcType="CHAR"/>
	  <result column="gender" property="gender" javaType="string" jdbcType="CHAR"/>
 	</resultMap>


    <!--配置查询所有用户的SQL -->
    <select id="getEmpById" resultMap="MyEmp">
        select * from employee where id=#{id}
    </select>
</mapper>


(1)resultMap和resultType只能存在一个
(2)很多高级查询中会用到resultMap

最后测试,发现数据库字段与javaBean字段可以正常映射。

1.5、resultMap关联查询之级联属性

需求:如果一个员工对应一个部门,那么在查询员工的同时如何关联查询出部门信息?

方式一:使用级联属性的方式

(1)创建一张员工表,一张部门表:
员工表emp:
在这里插入图片描述
部门表:
在这里插入图片描述

(2)新增javaBean类
emp类:
在这里插入图片描述

dept类:
在这里插入图片描述

(3)编写Dao接口
在这里插入图片描述

(4)MyBatis全局配置文件增加映射
在这里插入图片描述
(5)编写EmpMapper映射文件
在这里插入图片描述
(6)写测试案例:
在这里插入图片描述
打印的结果:
在这里插入图片描述

1.6、resultMap多对一关联查询之association

association定义关联对象封装

association可以指定联合的javaBean对象
  --property = “dept”,指定属性dept是联合对象
  --javaType = “com.xiaolang.bean.Dept”,指定属性对象的类型,不能省略不写
  --select:表明当前属性是调用select指定的方法查出结果
  --column:指定将那一列的值传给这个方法

使用association定义关联的单个对象的封装规则:
修改Mapper映射文件:
在这里插入图片描述
直接测试即可。

现在有一个需求,如果也有一个接口是getDeptById(Integer id)根据部门id来查部门信息,此时在不重写SQL的情况下想要查询员工信息和部门信>息该怎么做。
association可以指定联合的javaBean对象
  --property = “dept”,指定属性dept是联合对象
  --javaType = “com.xiaolang.bean.Dept”,指定属性对象的类型,不能省略不写
  --select:表明当前属性是调用select指定的方法查出结果
  --column:指定将那一列的值传给这个方法

分析:先查根据emp的id查询该员工所在部门,然后根据dept_id再去查询dept表。也可以使用association进行分布查询,此时在只需要修改Mapper映射文件:
在这里插入图片描述
这种方法的实质是执行了两句查询SQL来完成的。

1.7、resultMap关联查询之association分段查询&延迟加载

==需求:==在association分段查询中,我们每次查询employee对象的时候,都会将部门信息一起查出来。现在有个需求要实现需要部门信息的时候显示部门信息,不需要部门信息的时候只显示员工信息,该怎么做?
答案:可以使用延迟加载

懒加载的配置在MyBatis全局配置文件中设置。在以上使用association分别查询的基础上配置懒加载:
在这里插入图片描述
配置之后就会发现不会再执行查询部门信息的SQL。

1.8、resultMap一对多关联查询之collection

场景:根据部门id查询部门信息,并将该部门下的所有员工信息显示出来

知识
resultMap中的collection标签用来定义关联集合类型的属性封装规则
  --ofType:指定集合里面的元素类型

(1)deptBean增加属性:
在这里插入图片描述
(2)dept接口增加方法:
在这里插入图片描述
(3)MyBatis全局变量配置文件:
在这里插入图片描述
(4)deptMapper映射文件:
在这里插入图片描述
(5)测试:
在这里插入图片描述
结果:
在这里插入图片描述

1.9、resultMap一对多关联查询之collection&延迟加载

知识
resultMap中的collection标签用来定义关联集合类型的属性封装规则
  --ofType:指定集合里面的元素类型
  --select:指定调用的接口SQL
  --column:执行SQL后显示的字段名

(1)EmpDao新增:
在这里插入图片描述
(2)增加EmpMapper文件SQL:
在这里插入图片描述
(3)deptMapper文件中修改:
在这里插入图片描述
(4)测试:
在这里插入图片描述
结果:
在这里插入图片描述
collection的延迟加载需要设置:
在这里插入图片描述
除此之外,只需要打印出想要的详细就可以使用延迟加载,如:
只打印部门名称:
在这里插入图片描述
只执行了一个SQL:
在这里插入图片描述

1.10、resultMap分布查询传递多列值

无论是association还是collection以上演示的都是只传递一个参数的情况,如果是多个参数,需要怎么传递呢?

知识
涉及多值传递的时候,MyBatis会将多列值封装成map很传递:
column="{key1=column1,key2=column2}"
其中key1为SQL中的条件参数,column1为SQL中的变量

如果SQL为:
在这里插入图片描述
参数传递多个值为:
在这里插入图片描述

1.11、resultMap之discriminator鉴别器

discriminator鉴别器也是用于执行完SQL之后,对数据库返回的结果集MyBatis进行结果集处理或控制的一个语法

鉴别器:myBatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
场景:查询员工信息,如果查询的是女生,就把部门信息查询出来,如果是男生,把last_name这一列的值赋值给phone

Mapper文件中的resultMap和SQL这样写即可:

<resultMap id="empMapDis" type="com.xiaolang.bean.Emp">
        <id column="id" property="id" />
        <result column="last_name" property="last_name" />
        <result column="gender" property="gender" />
        <result column="phone" property="phone" />
        <!--
        discriminator
            column:指定需要判断的列名
            javaType:列名对应的java类型
        -->
        <discriminator javaType="string" column="gender">
            <!--如果是女生,查询出employee和部门信息-->
            <case value="0" resultType="com.xiaolang.bean.Emp">
                <association property="dept"
                             select="com.xiaolang.dao.IDeptDao.getDeptById"
                             column="dept_id"
                >
                </association>
            </case>
            <!--如果是男生,部门信息不查询,且把last_name的值赋值给phone-->
            <case value="1" resultType="com.xiaolang.bean.Emp">
                <id column="id" property="id" />
                <result column="last_name" property="last_name" />
                <result column="gender" property="gender" />
                <result column="last_name" property="phone" />
            </case>
        </discriminator>
    </resultMap>

    <select id="getEmpByIdStep" resultMap="empMapDis">
        select * from emp where id=#{id}
    </select>

二、MyBatis动态SQL

<resultMap>主要解决的是对数据库返回的结果集进行字段自定义映射,结果集过滤、结果集控制等操作。
优点:1、减少了SQL代码的编写和修改频率
   2、降低了数据库字段与Bean字段映射要求
   3、一定程度上可以减少与数据库的交互频率
缺点:resultMap配置复杂,知识冗余,容错率低,对于不熟悉resultMap配置的童鞋来说增加了开发或维护难度。
  那么接下来 一起来学习下动态SQL,对于不熟悉resultMap配置但是擅长编写SQL的人来说就说一大福利。

动态SQL:灵活的根据想要查询的结果集去执行不同的SQL,本质上和resultMap解决的是同一个问题。区别在于,resultMap注重对返回的结果集进行处理,而动态SQL注重对想要查询的目标结果灵活的分析,进行SQL的灵活拼装,再去执行SQL。

动态SQL使用的是OGNL语法,有点像JSP的EL表达式
常使用的动态SQL标签:

if 判断
choose(when,otherwise) 分支选择
trim(where,set) 字符串截取
foreach 循环
在动态SQL中遇到特殊字段,如:< 、> 、&等
需要写成转移字符,如:&lt;&gt;

常用转移字符如下:
在这里插入图片描述

2.1、动态SQL_if判断

标签常常作为拼装SQL的判断条件,配合OGNL语法使用。

场景:根据员工多个不同的查询条件查询用员工信息

(1)IEmpDao中新增查询接口:

    //根据组合条件查询员工信息
    public List<Emp> queryEmps(Emp employee);

(2)EmpMapper的映射文件可以这样写:

<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
    select * from emp A
    where 1=1
    <if test="id!=null">
        and id=#{id}
    </if>
    <if test="last_name !=null and last_name != '' ">
    <!--因为特殊字符在xml中需要写成转义字符,所以不能这样写
        <if test="last_name !=null && last_name != "" ">
        可以这样写:
        <if test="last_name !=null &amp;&amp; last_name != &quot;&quot; ">
    -->
        and last_name = #{last_name}
    </if>
    <if test="gender != 0 or gender != 1 ">
        and gender = #{gender}
    </if>
    <if test="phone != null and phone != '' ">
        and phone = #{phone}
    </if> 
</select>

测试即可。

2.2、动态SQL_where

  在以上例子中,我们使用了where 1=1和后面的查询条件来拼装,避免了没有id的情况下SQL拼接出错。
  下面我们使用第二种方案,使用where标签来将所有的包括在里面。myBatis就会将where标签中拼装的SQL,条件前面多出来的and或者or去掉。

知识where用于将where标签中的内容封装成一个条件

如下:

 <select id="queryEmps" resultType="com.xiaolang.bean.Emp">
 	select * from emp A
        <where>
            <if test="id!=null">
                and id=#{id}
            </if>
            <if test="last_name !=null and last_name != '' ">
                and last_name = #{last_name}
            </if>
            <if test="gender != null and gender != '' ">
                and gender = #{gender}
            </if>
            <if test="phone != null and phone != '' ">
                and phone = #{phone}
            </if>
        </where>
    </select>

测试即可。

2.3、动态SQL_trim自定义字符串截取

如果2.2的案例动态SQL这样写

 <select id="queryEmps" resultType="com.xiaolang.bean.Emp">
 	select * from emp A
        <where>
            <if test="id!=null">
                id=#{id} and 
            </if>
            <if test="last_name !=null and last_name != '' ">
                last_name = #{last_name} and 
            </if>
            <if test="gender != null and gender != '' ">
                gender = #{gender} and 
            </if>
            <if test="phone != null and phone != '' ">
                phone = #{phone}
            </if>
        </where>
    </select>

如果phone不给值,或为null,那么此时执行的SQL是这样的:
在这里插入图片描述
此时我们发现,如果and或or在条件后面,此时我们是无法解决问题的。那么我们如何解决这个问题呢?

知识
<trim>
  prefix:前缀。trim标签体中整个字符串拼串后的结果加上前缀。
  prefixOverrides:前缀覆盖。去掉整个字符串前面多余的字符
  suffix:后缀。给拼接后的整个字符串加一个后缀
  suffixOverrides:后缀覆盖。去掉整个字符串后面多余的字

需要使用trim解决上述问题,需要给整个字符串加上where作为前缀,然后使用去除整个字符串后悔and即可,所以可以这样写:

 <select id="queryEmps" resultType="com.xiaolang.bean.Emp">
 	select * from emp A
        <trim prefix="where" suffixOverrides="and">
            <if test="id!=null">
                and id=#{id}
            </if>
            <if test="last_name !=null and last_name != '' ">
                and last_name = #{last_name}
            </if>
            <if test="gender != null and gender != '' ">
                and gender = #{gender}
            </if>
            <if test="phone != null and phone != '' ">
                and phone = #{phone}
            </if>
        </trim>
    </select>

测试打印出来的SQL:
在这里插入图片描述

2.4、动态SQL_choose分支选择

动态SQL中的choose分支选择(when…otherwise)相当于java中switch…case语法。只会进入一个分支的语法。
场景:如果查询条件里面有id就按照id去查询,没有id就按照姓名去查,如果都没有就按照其他条件去查询。

代码:

<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
	select * from emp A
       <where>
       	<choose>
       		<when test="id != null">
       			id = #{id}
			</when>
			<when test="last_name != null">
				last_name = #{last_name}
			</when>
			<otherwise>
				gender = #{gender} and phone = #{phone}
			</otherwise>
       	</choose>
       </where>
   </select>

2.5、动态SQL_set封装条件

知识<set>标签是将标签中的语句封装成一个句子。
场景:根据员工id更新员工信息。

动态SQL为:

<update id="updateEmp">
        update emp
        set
        <if test="last_name !=null">
            last_name = #{last_name},
        </if>
        <if test="gender != null">
            gender = #{gender},
        </if>
        <if test="phone != null">
            phone = #{phone}
        </if>
        where id = #{id}
    </update>

当传值,phone为null时候,此时发现执行SQL之后的结果为:
在这里插入图片描述
为了解决以上条件后面多出来的“,”号,我们可以使用set标签。
如下:

 <update id="updateEmp">
    update emp
    <set>
        <if test="last_name !=null and last_name != '' ">
            last_name = #{last_name},
        </if>
        <if test="gender != null and gender != '' ">
            gender = #{gender},
        </if>
        <if test="phone != null and phone != '' ">
            phone = #{phone}
        </if>
    </set>
    where id = #{id}
</update>

这样即可解决问题,当然,也可以使用trim来进行操作:

 <update id="updateEmp">
    update emp
    <trim prefix="set" suffixOverrides=",">
        <if test="last_name !=null and last_name != '' ">
            last_name = #{last_name},
        </if>
        <if test="gender != null and gender != '' ">
            gender = #{gender},
        </if>
        <if test="phone != null and phone != '' ">
            phone = #{phone}
        </if>
    </trim>
    where id = #{id}
</update>

2.6、动态SQL_foreach遍历集合

知识<foreach>
--------------collection:指定要遍历的集合。list类型参数会特殊处理封装在map中,map的key就叫list
--------------item:将当前遍历的元素赋值给当前变量
--------------separator:每个元素之间的分隔符
--------------open:遍历出所有结果,在结果头部拼接一个开始的字符
--------------close:遍历出所有结果,在结果尾部拼接一个开始的字符
--------------index:索引。遍历list的时候是索引,item就是当前值。遍历map的时候表示的是map的key,item就是map的值。

例:
IEmpDao接口:

//查询指定id的员工信息
public List<Emp> queryEmpInfo(@Param("list_ids") List<Integer> ids);

EmpMapper:

<select id="queryEmpInfo" resultType="com.xiaolang.bean.Emp">
    select * from emp where id in
        <foreach collection="list_ids" item="item_id"
                 separator="," open="(" close=")">

            #{item_id}
        </foreach>
</select>

测试:
在这里插入图片描述
结果:
在这里插入图片描述
注意:foreanch 标签中的 collection的值要对应mapper.java中的@param("") 里面的值。

2.7、MySQL_foreach批量插入两种方式

MySQL数据库支持插入两条或两条以上的SQL为:

insert into emp(id, last_name, gender, phone, dept_id) values(104,"皮城女警",'0',12346784908,2) , (105,"德邦总管",'1',123462108,4)

那么如何使用foreach批量插入数据呢?

方法一
(1)IEmpDao:

//批量新增员工
public void insertEmps(@Param("emp_list")List<Emp> emps);

(2)Mapper:

<insert id="insertEmps">
     insert into emp(id, last_name, gender, phone, dept_id)
     values
     <foreach collection="emp_list" item="emp_item" separator=",">
         (
          #{emp_item.id}, #{emp_item.last_name}, #{emp_item.gender},
          #{emp_item.phone}, #{emp_item.dept.dept_id}
         )
     </foreach>
 </insert>

方式二:
Mapper的Sql这样写:

<insert id="insertEmps">
    <foreach collection="emp_list" item="emp_item" separator=";">
        insert into emp(id, last_name, gender, phone, dept_id)
        values
        (
        #{emp_item.id}, #{emp_item.last_name}, #{emp_item.gender},
        #{emp_item.phone}, #{emp_item.dept.dept_id}
        )
    </foreach>
</insert>

更推荐使用第一种写法
同样,foreach可以用于批量修改,批量删除。

2.8、Oracle_foreach批量插入两种方式

Oracle数据库支持下面语法:

insert into emp(id, last_name, gender, phone, dept_id) values(104,"皮城女警",'0',12346784908,2) , (105,"德邦总管",'1',123462108,4)

但是Oracle支持这样的语法:

begin
  insert into emp(id, last_name, gender, phone) values(104,"皮城女警",'0',12346784908,2);
  insert into emp(id, last_name, gender, phone) values(105,"德邦总管",'1',123462108);
end;

所以,方式一:

<insert id="insertEmpsBeach">
      begin
      <foreach collection="emp_list" item="emp_item">
          insert into emp(id, last_name, gender, phone)
          values(#{emp_seq.nextval}, #{emp_item.last_name}, #{emp_item.gender}, #{emp_item.phone});
      </foreach>
     end
  </insert>

当然也可以这样:

  <insert id="insertEmpsBeach">
      <foreach collection="emp_list" item="emp_item" open="begin" close="end">
          insert into emp(id, last_name, gender, phone)
          values(#{emp_seq.nextval}, #{emp_item.last_name}, #{emp_item.gender}, #{emp_item.phone});
      </foreach>
  </insert>

Oracle批量插入数据方式二:利用中间表(这种方式比较麻烦)
Oracle还支持这样写:

insert into emp(id, last_name, phone)
  select emp_seq.nextval, last_name, phone from (
   select '张三' last_name, '13678705044' phone from dual
   union
   select '李四' last_name, '18678705044' phone from dual
   union
   select '李四' last_name, '18678705044' phone from dual
   union

所以,Mapper中的SQL这样写:

 <insert id="insertEmpsBeach">
     insert into emp(id, last_name, phone)
         select  emp_seq.nextval, last_name, phone from (
             <foreach collection="emp_list" item="emp_item" separator="union">
                 select #{emp_item.last_name} last_name, #{emp_item.phone} phone from dual
             </foreach>
         )
 </insert>

2.9、动态SQL_内置参数

  在动态SQL中,不只是方法传递过来的参数可以用来判断和取值。
myBatis默认还有两个内置参数:
1、_parameter:代表整个参数
  单个参数:_parameter代表的就是参数本身
  多个参数:参数会被封装成一个map,_parameter就代表这个map
2、_datanaseId:如果在MyBatis全局配置里面配置了databaseIdProvider标签
  _datanaseId就是代表当前数据库的别名
如图:
在这里插入图片描述

那么我们可以根据这个默认参数作为判断条件,只写一个动态SQL:

<select id="queryEmp" resultType="com.xiaolang.bean.Emp">
    <if test="_databaseId == mysql">
        select * from emp
        <if test="_parameter != null">
            where id = #{id}
            <!--也可以这样写  where id = #{_parameter.id} -->
        </if>
    </if>
    <if test="_databaseId == oracle">
        select * from emp where id = #{id}
     </if>
</select>

2.9、动态SQL_bind绑定

知识:bind绑定,可以将OGNL表达式的值 绑定到一个变量中,方便后来引用这个变量值
场景:现在有一个模糊查询,想要在SQL这样这样输出
select * from emp where last_name like '%e%'
但是last_name是变量,我们如何来写SQL。
方案一:where last_name like '% #{last_name}%' 错误
方案二:where last_name like '% ${last_name}%' 正确,但是不安全
方案三: 使用bind标签 正确,如下:

在这里插入图片描述

2.10、动态SQL_抽取重用sql片段

知识:
(1)<sql>标签用于定义重复使用的SQL代码段
(2)<include>标签用于引用重复使用的片段
注:sql标签内也可以写<if>、<when>等标签
用途:用于经常复用的sql代码片段

例如:
原SQL为:

insert into emp(id, last_name, phone)
values(#{id}, #{last_name}, #{phone}) 

抽出复用的SQL代码:

 <sql id="insertColumn">
     <if test="_databaseId == mysql">
         id, last_name, phone
     </if>
     <if test="_databaseId == oracle">
         id, last_name, email
     </if>
 </sql>

复用在其他SQL中:

<insert id="insertEmps">
     insert into emp( <include refid="insertColumn"></include> )
     values
     <foreach collection="emp_list" item="emp_item" separator=",">
         (
         #{emp_item.id}, #{emp_item.last_name},#{emp_item.phone}
         )
     </foreach>
 </insert>

include还可以自定义属性,例如:

 <sql id="insertColumn">
     <if test="_databaseId == mysql">
         id, last_name, phone, ${birthday}
     </if>
     <if test="_databaseId == oracle">
         id, last_name, email
     </if>
 </sql>

复用:
在这里插入图片描述

发布了22 篇原创文章 · 获赞 16 · 访问量 2902

猜你喜欢

转载自blog.csdn.net/qq_25083447/article/details/104297761