MyBatis笔记(三)MyBatis动态SQL详解

目录

    • 一、背景
    • 二、if 语句
      • 2.1、实现常规if操作
      • 2.2、实现常规if操作(可能异常)
    • 三、where + if 语句
      • 3.1、实现动态where + if 操作
    • 四、set + if 语句
      • 4.1、实现更新操作
    • 五、trim 语句
      • 5.1、改造更新操作的set
      • 5.2、改造where+if 操作
    • 六、choose + when + otherwise 语句
      • 6.1、实现switch条件操作
    • 七、 foreach 语句
      • 7.1、实现in操作
      • 7.2、实现or操作
    • 八、SQL 语句
      • 8.1、实现定义代码块
    • 九、bind 语句
    • 结语

一、背景

  本文主要介绍 MyBatis 动态SQL,它是MyBatis 的强大特性之一,它可以帮你解决不同条件拼接 SQL 语句,比如空格,逗号,单双引号,各种语句关键字等,利用 MyBatis 动态 SQL,可以轻松处理掉。如果你还对于MyBatis基本使用不了解的可以参考我我之前的文章:
  MyBatis笔记(一)Spring Boot整合MyBatis实现增删查改详解(入门版)
  MyBatis笔记(二)MyBatis参数传递详解

  本文中案例返回结果(com.alian.mybatissql.dto.EmployeeDto)的一个一个映射如下:

EmployeeDto.java

package com.alian.mybatissql.dto;

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDate;

@Data
public class EmployeeDto implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 员工编号
     */
    private String id;

    /**
     * 员工姓名
     */
    private String name;

    /**
     * 员工年龄
     */
    private int age;

    /**
     * 工资
     */
    private double salary;

    /**
     * 部门
     */
    private String department;

    /**
     * 入职时间
     */
    private LocalDate hireDate;

}

二、if 语句

2.1、实现常规if操作

  EmployeeMapper.java接口定义如下:

    /**
     * 根据名字查找员工,如果又部门则部门一起参与查询
     */
    List<EmployeeDto> getByNameWithDepartment(@Param("name") String name, @Param("department") String department);

  EmployeeMapper.xml里对应的配置如下:

    
    <select id="getByNameWithDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		WHERE
			e.emp_name= #{name}
		<if test="department != null and department!=''">
			AND e.department = #{department}
		if>
  	select>

  当传入的department 为空,表示 标签的值为false,不会拼接里面的语句,此时的查询语句为:

SELECT 
e.id, e.emp_name as name, e.age, e.salary, e.department, e.hire_date as hireDate 
FROM tb_inf_employee e 
WHERE 
e.emp_name= #{name};

  当传入的department 不为空,表示 标签的值为true,会拼接里面的语句,此时的查询语句为:

SELECT 
e.id, e.emp_name as name, e.age, e.salary, e.department, e.hire_date as hireDate 
FROM tb_inf_employee e 
WHERE 
e.emp_name= #{name} AND e.department = #{department};

2.2、实现常规if操作(可能异常)

  EmployeeMapper.java接口定义如下:

    /**
     * 根据名字查或者部门查询员工信息
     */
    List<EmployeeDto> getByNameAndDepartment(@Param("name") String name, @Param("department") String department);

  EmployeeMapper.xml里对应的配置如下:

	
	<select id="getByNameAndDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		WHERE
		<if test="name != null and name!=''">
			e.emp_name= #{name}
		if>
		<if test="department != null and department!=''">
			AND e.department = #{department}
		if>
	select>

  当name不为空时,不管department 有没有值都可以执行,但是当name为空,department 不为空时就有问题了,此时的查询语句变成:

SELECT 
e.id, e.emp_name as name, e.age, e.salary, e.department, e.hire_date as hireDate 
FROM tb_inf_employee e 
WHERE 
AND e.department = #{department};

  从语句我们也知道这是一个明显的错误语句,有些小伙伴可能会这样做(在where后面添加一个恒为true的条件,之后的 里的语句都用AND开头):

	
	<select id="getByNameAndDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		WHERE 1=1
		<if test="name != null and name!=''">
			AND e.emp_name= #{name}
		if>
		<if test="department != null and department!=''">
			AND e.department = #{department}
		if>
	select>

  这时不管两个值为为不为空都不会有问题了,当然这不是最优解,MyBaits也想到了,也就是我们接下里要说的 where + if 语句

三、where + if 语句

3.1、实现动态where + if 操作

  EmployeeMapper.java接口定义如下(还是上一个示例):

    /**
     * 根据名字查或者部门查询员工信息
     */
    List<EmployeeDto> getByNameAndDepartment(@Param("name") String name, @Param("department") String department);

  EmployeeMapper.xml里对应的配置如下:

	
	<select id="getByNameAndDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		<where>
			<if test="name != null and name!=''">
				e.emp_name= #{name}
			if>
			<if test="department != null and department!=''">
				AND e.department = #{department}
			if>
		where>
	select>
  • 如果 标签包含的标签中有返回值(也就是 标签中的test为true)的话,它就插入一个 where
  • 如果 标签里的标签返回的内容是以 AND OR 开头的,则它会自动剔除掉

  从上面的xml配置上看到,我们的SQL直接省略了那个恒为true条件,即算第一句加上 AND也能正常运行,因为MyBaits已经帮我们处理好了。

四、set + if 语句

4.1、实现更新操作

  EmployeeMapper.java接口定义如下:

    /**
     * 根据id更新部门和薪资
     */
    int updateDepartmentAndSalaryById(@Param("id") String id, @Param("department") String department,
    								  @Param("salary") double salary);

  EmployeeMapper.xml里对应的配置如下:

	<update id="updateDepartmentAndSalaryById">
		update tb_inf_employee e
		<set>
			<if test="department != null and department!=''">
				e.department = #{department},
			if>
			<if test="salary > 0">
				e.salary= #{salary},
			if>
		set>
		<where>
			e.id= #{id}
		where>
	update>
  • 如果 标签包含的标签中有返回值(也就是 标签中的test为true)的话,它就插入一个 set
  • 如果 标签包含的标签返回值的内容是以逗号结尾的,则它会自动剔除掉

  如果所有的条件同时为空,那么 就不会满足,而执行不了,所以需要业务在做更新操作的时候对参数进行校验,不然操作毫无意义。实际上还有一种方法可以实现上述的效果,那就是接下里要说的 标签了。

五、trim 语句

5.1、改造更新操作的set

  EmployeeMapper.java接口定义如下:

    /**
     * 根据id更新部门和薪资
     */
    int updateDepartmentAndSalaryById(@Param("id") String id, @Param("department") String department,
    								  @Param("salary") double salary);

  EmployeeMapper.xml里对应的配置如下:

	<update id="updateDepartmentAndSalaryById">
		update tb_inf_employee e
		<trim prefix="set" suffixOverrides=",">
			<if test="department != null and department!=''">
				e.department = #{department},
			if>
			<if test="salary > 0">
				e.salary= #{salary},
			if>
		trim>
		<where>
			e.id= #{id}
		where>
	update>
  • 标签可以自动添加前缀,这里的前缀是set(prefix=“set”
  • 标签可以自动删除 标签内的返回值里的后缀,这里返回值的后缀可能有逗号suffixOverrides=","

  这么看来,这个 标签还挺强大了,也挺实用的,之前我们也用到了where + if 语句,是不是也可以改造呢?

5.2、改造where+if 操作

  EmployeeMapper.java接口定义如下:

    /**
     * 根据名字查或者部门查询员工信息
     */
    List<EmployeeDto> getByNameAndDepartment(@Param("name") String name, @Param("department") String department);

  EmployeeMapper.xml里对应的配置如下:

	<select id="getByNameAndDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		<trim prefix="where" prefixOverrides="and">
			<if test="name != null and name!=''">
				AND e.emp_name= #{name}
			if>
			<if test="department != null and department!=''">
				AND e.department = #{department}
			if>
		trim>
	select>
  • 标签可以自动添加前缀,这里前缀是where(prefix=“where”
  • 标签可以自动删除删除 标签内的返回值里的前缀,这里返回值的后缀可能有ANDprefixOverrides=“and”

六、choose + when + otherwise 语句

6.1、实现switch条件操作

  EmployeeMapper.java接口定义如下:

    /**
     * 根据给定id或姓名或部门信息查询员工信息
     */
    List<EmployeeDto> getByIdOrNameOrDepartment(@Param("id") String id, @Param("name") String name, 
    											@Param("department") String department);

  EmployeeMapper.xml里对应的配置如下:

	
	<select id="getByIdOrNameOrDepartment" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		<where>
			<choose>
				<when test="id != null and id!=''">
					e.id = #{id}
				when>
				<when test="name != null and name!=''">
					e.emp_name = #{name}
				when>
				<otherwise>
					e.department = #{department}
				otherwise>
			choose>
		where>
	select>

  这段代码的意思是:

  • 如果员工id不为空,则按照员工id查询员工信息
  • 如果员工id为空,员工姓名不为空,则按照员工的姓名查询员工信息
  • 如果员工id为空,员工姓名也为空,则按照部门查询员工信息

实际这种结构和java里的switch结构很像:

    switch (条件) {
        case 员工id不为空:
            //根据id查询员工
            break;
        case 员工姓名不为空:
            //根据姓名查询员工
            break;
        default: //根据部门查询员工
    }

七、 foreach 语句

7.1、实现in操作

  EmployeeMapper.java接口定义如下:

    /**
     * 根据id列表查询员工
     */
    List<EmployeeDto> getByIdIn(@Param("idList") List<String> idList);

  EmployeeMapper.xml里对应的配置如下:

	
	<select id="getByIdIn" resultType="EmployeeDto">
		SELECT
			e.id,
			e.emp_name as name,
			e.age,
			e.salary,
			e.department,
			e.hire_date as hireDate
		FROM
			tb_inf_employee e
		where
		<foreach collection="idList" item="item" index="index" open="e.id IN(" separator="," close=")">
			#{item}
		foreach>
	select>

  实际执行语句如下:

SELECT e.id, e.emp_name as name, e.age, e.salary, e.department, e.hire_date as hireDate 
FROM tb_inf_employee e 
where e.id IN( ? , ? , ? )

关于 标签

  • collection :要foreach的对象,比如List
  • item:循环体中的具体对象
  • index:在list和数组中,index是元素的序号,在map中,index是元素的key
  • open:foreach代码的开始符号,一般是 ( 和close=")"合用。常用在in(),values()时。该参数可选。
  • separator:元素之间的分隔符,比如逗号
  • close:foreach代码的关闭符号,一般是 ) 和open="("合用。常用在in(),values()时
  • 7.2、实现or操作

      EmployeeMapper.java接口定义如下:

        /**
         * 根据id列表查询员工
         */
        List<EmployeeDto> getByIdOr(@Param("idList") List<String> idList);
    

      EmployeeMapper.xml里对应的配置如下:

    	
    	<select id="getByIdOr" resultType="EmployeeDto">
    		SELECT
    			e.id,
    			e.emp_name as name,
    			e.age,
    			e.salary,
    			e.department,
    			e.hire_date as hireDate
    		FROM
    			tb_inf_employee e
    		where
    		<foreach collection="idList" item="item" index="index" separator="or" >
    			e.id = #{item}
    		foreach>
    	select>
    

      实际执行语句如下:

    SELECT e.id, e.emp_name as name, e.age, e.salary, e.department, e.hire_date as hireDate 
    FROM tb_inf_employee e 
    where e.id = ? or e.id = ? or e.id = ?
    

    八、SQL 语句

    8.1、实现定义代码块

      EmployeeMapper.java接口定义如下:

        /**
         * 查询所有员工列表
         */
        List<EmployeeDto> findAll();
    

      EmployeeMapper.xml里对应的配置如下:

    	<sql id="fieldList">e.id,e.emp_name as name,e.age,e.salary,e.department,e.hire_date as hireDatesql>
    
    	<select id="findAll" resultType="EmployeeDto">
    		SELECT
    			<include refid="fieldList">include>
    		FROM
    			tb_inf_employee e
      	select>
    

      之前我们示例里的字段一直都在重复的写,这个时候,我们就可以直接调用定义的 片段,增加了代码重用性,简化了代码。

    九、bind 语句

      EmployeeMapper.java接口定义如下:

        /**
         * 根据姓名模糊查询员工
         */
        List<EmployeeDto> getByNameLikeWithBindSql(@Param("name") String name);
    

      EmployeeMapper.xml里对应的配置如下:

    	<select id="getByNameLikeWithBindSql" resultType="EmployeeDto">
    		<bind name="pattern" value="'%' + name + '%'"/>
    		SELECT
    			e.id,
    			e.emp_name as name,
    			e.age,
    			e.salary,
    			e.department,
    			e.hire_date as hireDate
    		FROM
    			tb_inf_employee e
    		where
    			e.emp_name like #{pattern}
    	select>
    

      需要注意的是#{pattern},这里的pattern是上面定义的bind标签的name,而不是接口定义的name。我这里是演示bind标签的使用,实际的模糊查询不会这么写:

    	<select id="getByNameLike" resultType="EmployeeDto">
    		SELECT
    			e.id,
    			e.emp_name as name,
    			e.age,
    			e.salary,
    			e.department,
    			e.hire_date as hireDate
    		FROM
    			tb_inf_employee e
    		where
    			e.emp_name like CONCAT('%',#{name},'%')
    	select>
    

    结语

       MyBatis 动态SQL就说到这里,主要还是要实际使用,加深理解,后续有时间我们继续研究其他方面的。

    你可能感兴趣的:(MyBatis笔记,mybatis,动态SQL)