MyBatis 动态SQL全面详解


Posted in MySQL onOctober 05, 2021

前言

前面mysql都是通过静态sql进行查询的,但是如果业务复杂的时候,我们会遇到引号问题,或者多一个空格,这就使得sql代码编写错误了,所以为了解决这个问题,我们有了动态sql。

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。具体是通过标签来实现的。

动态sql

1.先看一下模块目录结构

在类路径的resources下的mapper包下创建sql.xml文件(共性抽取)

MyBatis 动态SQL全面详解

2.物理建模和逻辑建模

这里省略物理建模步骤,要求数据库的表与pojo类要对应。

package pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    private Integer empId;
    private String empName;
    private Double empSalary;

}

3. 引入依赖

把之前的log4j复制到类路径resouces下,另外我们引入依赖后的pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>day03-mybatis02-dynamic</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>

        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>

        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.3</version>
            <scope>runtime</scope>
        </dependency>

        <!-- log4j日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

</project>

4.全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--驼峰映射-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--类型别名映射-->
    <typeAliases>
        <package name="pojo"/>
    </typeAliases>
    <!--环境配置-->
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="username" value="root"/>
                <property name="password" value="888888"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="driver" value="com.mysql.jdbc.Driver"/>
            </dataSource>
                
        </environment>
    </environments>
    <!--路径映射-->
    <mappers>
        <mapper resource="mapper/sql.xml"/>
        <package name="mapper"/>
    </mappers>
</configuration>

注意: 这里有驼峰映射,别名映射,路径映射和路径映射。和以前的不同的是,我们这里做了sql语句的共性抽取,所以得加一个sql的路径映射 <mapper resource="mapper/sql.xml"/>

5.sql共性抽取文件

在类路径resources下的包mapper下创建一个sql.xml(因为我们sql是要写在映射文件中,自己本身也是映射文件,所以需要写在mapper下)。到要用的时候,在映射路径文件中需要用到这个sql语句的地方加入 <include refid="mapper.sql.mySelectSql"></include>

<?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="mapper.sql">

        <sql id="mySelectSql">
    select emp_id,emp_name,emp_salary from t_emp
</sql>


</mapper>

共性抽取文件也可以不配置,这时候直接在映射文件中把要执行的语句重新编写就行了。

6.mapper接口

一共有七个方法

package mapper;

import org.apache.ibatis.annotations.Param;
import pojo.Employee;

import java.util.List;

public interface EmployeeMapper {

     //根据员工的empId查询大于该empId的所有员工,如果empId为null,则查询全体员工
    List<Employee> selectEmployeeListByEmpId(Integer empId);

    /**
     * 查询大于传入的empId并且工资大于传入的empSalary的员工集合,如果传入的empId为null,则不考虑empId条件
     * 传入的empSalary为null则不考虑empSalary的条件
     */
    List<Employee> selectEmployeeListByEmpIdAndEmpSalary(@Param("empId") Integer empId, @Param("empSalary") Double empSalary);

    /**
     * 根据empId更新员工信息,如果某个值为null,则不更新这个字段
     */
    void updateEmployee(Employee employee);

    /**
     * 根据emp_id查询员工信息,如果0<emp_id<6,那么就查询所有大于该emp_id的员工,如果emp_id是大于6,那么就查询所有小于该emp_id的员工
     * 如果是其它情况,则查询所有员工信息
    
     */
    List<Employee> selectEmployeeList(Integer empId);

    /**
     * 添加员工信息 
     */
    void insertEmployee(Employee employee);

    /**
     * 批量添加员工集合    
     */
    void insertEmployeeList(@Param("employeeList") List<Employee> employeeList);

    /**
     * 根据员工的id集合查询员工集
     */
    List<Employee> selectEmployeeListByEmpIdList(List<Integer> idList);
}

if

目标:根据员工的empId查询大于该empId的所有员工,如果empId为null,则查询全体员工。

Dao接口的方法为:
List<Employee> selectEmployeeListByEmpId(Integer empId);

静态sql:

<select id="selectEmployeeListByEmpId" resultType="Employee">

    <include refid="mapper.sql.mySelectSql"></include> where emp_id>#{empId}

</select>

动态sql:

<select id="selectEmployeeListByEmpId" resultType="Employee">
     <include refid="mapper.sql.mySelectSql"></include>
     <if test="empId != null">
         where emp_id>#{empId}
     </if>
 </select>

<include refid="mapper.sql.mySelectSql"></include>表示引用抽取出的sql片段,也可以直接写sql语句。如果是静态sql,当id为null时,查询出来的是空,动态sql则可以查出全部。if标签里面有test属性名,作为判断语句。

where

目标:

  • 查询大于传入的empId并且工资大于传入的empSalary的员工集合
  • 如果传入的empId为null,则不考虑empId条件
  • 传入的empSalary为null则不考虑empSalary的条件

Dao接口方法:

List<Employee> selectEmployeeListByEmpIdAndEmpSalary(@Param("empId") Integer empId, @Param("empSalary") Double empSalary);

用if标签的动态sql:

<select id="selectEmployeeListByEmpIdAndEmpSalary" resultType="Employee">
    <include refid="mapper.sql.mySelectSql"></include> where
   <if test="empId != null">
            emp_id>#{empId}
        </if>
        <if test="empSalary != null">
           and emp_salary>#{empSalary}
        </if>

这里可以看到,如果empSalary为空,那么sql语句为select * from t_emp where emp_id >#{empId},但是如果empId为空,那么sql语句为select * from t_emp where and emp_salary>#{empSalary},很明显这个是错的,if标签在这里就不适用了。所以我们用where标签,或者trim标签。

where和if的动态sql:

<select id="selectEmployeeListByEmpIdAndEmpSalary" resultType="Employee">
    <include refid="mapper.sql.mySelectSql"></include>
   
    <where>
        <if test="empId != null">
            emp_id>#{empId}
        </if>
        <if test="empSalary != null">
           and emp_salary>#{empSalary}
        </if>
    </where>
</select>

where标签的作用:

  • 在第一个条件之前自动添加WHERE关键字
  • 自动去掉第一个条件前的连接符(AND、OR等等)

trim

trim是修建的意思,其实就是去头去尾,这里还是根据上面那个方法

trim的动态sql

<select id="selectEmployeeListByEmpIdAndEmpSalary" resultType="Employee">
    <include refid="mapper.sql.mySelectSql"></include>
    <trim prefix="WHERE" prefixOverrides="AND|OR">
        <if test="empId != null">
            emp_id>#{empId}
        </if>

        <if test="empSalary != null">
            AND emp_salary>#{empSalary}
        </if>
    </trim>
</select>

trim标签:

  • prefix:指定要动态添加的前缀
  • suffix属性:指定要动态添加的后缀
  • prefixOverrides:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
  • suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值

set

目标:根据empId更新员工信息,如果某个值为null,则不更新这个字段

Dao接口方法:
void updateEmployee(Employee employee);
我们先用上面的trim标签来解决一下这个问题,

trim的动态sql:

<update id="updateEmployee" >
    <trim prefix="set" prefixOverrides=",">
        <if test="empName!=null">
            emp_name=#{empName}
        </if>
        <if test="empSalary!=null">
            , emp_salary=#{empSalary}
        </if>
    </trim>
    where emp_id=#{empId}
</update>

set的动态sql

<update id="updateEmployee" >
    update t_emp
     <set >
         <if test="empName!=null">
             emp_name=#{empName}
         </if>
         <if test="empSalary!=null">
            , emp_salary=#{empSalary}
         </if>
     </set>

可以看出

set标签的作用:

  • 自动在要修改的第一个字段之前添加SET关键字
  • 去掉要修改的第一个字段前的连接符(,)

choose、when、otherwise

目标:

  • 根据emp_id查询员工信息,如果0<emp_id<6,那么就查询所有大于该emp_id的员工
  • 如果emp_id是大于6,那么就查询所有小于该emp_id的员工
  • 如果是其它情况,则查询所有员工信息

Dao接口方法:
List<Employee> selectEmployeeList(Integer empId);

动态sql

<select id="selectEmployeeList" resultType="employee">
  
    <include refid="mapper.sql.mySelectSql"></include> where
    <choose>
    <!--&lt;是<号的转义字符-->
        <when test="empId>0 and empId&lt;6">
            emp_id>#{empId}
        </when>
        <when test="empId>6">
            emp_id&lt;#{empId}
        </when>
        <otherwise>
            1==1
        </otherwise>
    </choose>

</select>

choose、when、otherwise
相当于if ... else if... else if ... else

  • 如果某一个when的条件成立,则不会继续判断后续的when
  • 如果所有的when都不成立,则会拼接otherwise标签中的内容

foreach

目标1:批量添加员工信息

Dao接口方法:

void insertEmployeeList(@Param("employeeList") List employeeList);

1.动态sql

<insert id="insertEmployeeList">
    insert into t_emp(emp_name,emp_salary)values
    <!--collection标签可以写list,collection,
    或者自己自己定义参数名@Param("employeeList") List<Employee> employeeList-->
    <foreach collection="employeeList" separator="," item="emp">
        (#{emp.empName},#{emp.empSalary})
    </foreach>
</insert>

目标2:根据多个id查询多个员工信息

Dao接口

List selectEmployeeListByEmpIdList(List idList);

2.动态sql

<select id="selectEmployeeListByEmpIdList" resultType="employee">
    <include refid="mapper.sql.mySelectSql"></include>
     <foreach collection="collection" item="id" separator="," open="where emp_id in (" close=")">
         #{id}
     </foreach>
</select>

批量查询:foreach标签

  1. collection属性: 表示要遍历的对象,如果要遍历的参数使用@Param注解取名了就使用该名字,如果没有取名List,或者collection。
  2. item属性: 表示遍历出来的元素,我们到时候要拼接SQL语句就得使用这个元素: 如果遍历出来的元素是POJO对象, 那么我们就通过 #{遍历出来的元素.POJO的属性} 获取数据;如果遍历出来的元素是简单类型的数据,那么我们就使用 #{遍历出来的元素} 获取这个简单类型数据
  3. separator属性: 遍历出来的元素之间的分隔符
  4. open属性: 在遍历出来的第一个元素之前添加前缀
  5. close属性: 在遍历出来的最后一个元素之后添加后缀

测试程序

import mapper.EmployeeMapper;
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 org.junit.After;
import org.junit.Before;
import pojo.Employee;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Test {
    private EmployeeMapper employeeMapper;
    private InputStream is;
    private SqlSession sqlSession;
    @Before
    public void init() throws Exception{
        //目标:获取EmployeeMapper接口的代理对象,并且使用该对象调用selectEmployee(1)方法,然后返回Employee对象
        //1. 将全局配置文件转成字节输入流
        is = Resources.getResourceAsStream("mybatisConfig.xml");
        //2. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //3. 使用构建者模式创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //4. 使用工厂模式创建一个SqlSession对象
        sqlSession = sqlSessionFactory.openSession();
        //5. 使用动态代理模式,创建EmployeeMapper接口的代理对象
        employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    }


    @After
    public void after() throws Exception{
        //提交事务!!!
        sqlSession.commit();
        //7. 关闭资源
        is.close();
        sqlSession.close();
    }

    @org.junit.Test
    public void testSelectEmployeeListByEmpId(){
        System.out.println(employeeMapper.selectEmployeeListByEmpId(null));
    }

    @org.junit.Test
    public void testSelectEmployeeListByEmpIdAndEmpSalary(){
        System.out.println(employeeMapper.selectEmployeeListByEmpIdAndEmpSalary(2, 300d));
    }

    @org.junit.Test
    public void testUpdateEmployee(){
        Employee employee = new Employee(3,"celia", 9000d);

        employeeMapper.updateEmployee(employee);
    }

    @org.junit.Test
    public void testSelectEmployeeList(){
    System.out.println(employeeMapper.selectEmployeeList(7));
}

   @org.junit.Test
   public void testInsertEmployee(){
        employeeMapper.insertEmployee(new Employee(null,"tom",300d));
    }

    @org.junit.Test
    public void testInsertEmployeeList(){
        List<Employee> employeeList = new ArrayList<>();
        for (int i = 11; i <=20 ; i++) {
            employeeList.add(new Employee(null,"aobama"+i,2000d));
        }

        employeeMapper.insertEmployeeList(employeeList);

    }

    @org.junit.Test
    public void testSelectEmployeeListByEmpIdList(){
        List<Integer> idList = new ArrayList<>();
        idList.add(23);
        idList.add(33);
        idList.add(32);
        idList.add(21);
        idList.add(22);
        System.out.println(employeeMapper.selectEmployeeListByEmpIdList(idList));
    }


}

到此这篇关于MyBatis 动态SQL全面详解的文章就介绍到这了,更多相关MyBatis 动态SQL内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL infobright的安装步骤
Apr 07 MySQL
MySQL安装后默认自带数据库的作用详解
Apr 27 MySQL
MySQL sql_mode的使用详解
May 08 MySQL
详解MySQL数据库千万级数据查询和存储
May 18 MySQL
MySQL 存储过程的优缺点分析
May 20 MySQL
MySQL 查询速度慢的原因
May 25 MySQL
Mysql数据库索引面试题(程序员基础技能)
May 31 MySQL
MySQL中的布尔值,怎么存储false或true
Jun 04 MySQL
一条 SQL 语句执行过程
Mar 17 MySQL
在MySQL中你成功的避开了所有索引
Apr 20 MySQL
mysql 索引的数据结构为什么要采用B+树
Apr 26 MySQL
MySQL 数据表操作
May 04 MySQL
MySQL中datetime时间字段的四舍五入操作
mysql如何能有效防止删库跑路
Oct 05 #MySQL
浅谈MySQL表空间回收的正确姿势
浅谈MySQL函数
Oct 05 #MySQL
Mysql binlog日志文件过大的解决
Oct 05 #MySQL
MySQL修炼之联结与集合浅析
MySQL分库分表详情
You might like
php数组函数序列 之array_count_values() 统计数组中所有值出现的次数函数
2011/10/29 PHP
PHP实现登陆表单提交CSRF及验证码
2017/01/24 PHP
PHP使用XMLWriter读写xml文件操作详解
2018/07/31 PHP
List the Stored Procedures in a SQL Server database
2007/06/20 Javascript
node.js中的fs.mkdir方法使用说明
2014/12/17 Javascript
js实现select下拉框菜单
2015/12/08 Javascript
AngularJS ng-bind 指令简单实现
2016/07/30 Javascript
详解AngularJS controller调用factory
2017/05/19 Javascript
深入理解Node中的buffer模块
2017/06/03 Javascript
JavaScript利用fetch实现异步请求的方法实例
2017/07/26 Javascript
详解win7 cmd执行vue不是内部命令的解决方法
2017/07/27 Javascript
JS 音频可视化插件Wavesurfer.js的使用教程
2018/10/31 Javascript
vue实现的树形结构加多选框示例
2019/02/02 Javascript
微信小程序结合mock.js实现后台模拟及调试
2019/03/28 Javascript
javascript异步编程的六种方式总结
2019/05/17 Javascript
微信小程序加载机制及运行机制图解
2019/11/27 Javascript
tracking.js实现前端人脸识别功能
2020/04/16 Javascript
使用Python判断IP地址合法性的方法实例
2014/03/13 Python
python 对给定可迭代集合统计出现频率,并排序的方法
2018/10/18 Python
详解Python 正则表达式模块
2018/11/05 Python
Python判断telnet通不通的实例
2019/01/26 Python
python实现文件助手中查看微信撤回消息
2019/04/29 Python
python实现websocket的客户端压力测试
2019/06/25 Python
使用turtle绘制五角星、分形树
2019/10/06 Python
Python @property装饰器原理解析
2020/01/22 Python
sklearn+python:线性回归案例
2020/02/24 Python
销售人员中英文自荐信
2013/09/22 职场文书
计算机专业个人简短的自我评价
2013/10/23 职场文书
项目经理的岗位职责
2013/11/23 职场文书
端午节活动总结报告
2015/02/11 职场文书
公司仓管员岗位职责
2015/04/01 职场文书
2015年绩效考核工作总结
2015/05/23 职场文书
2019企业文化管理制度范本!
2019/08/06 职场文书
weblogic服务建立数据源连接测试更新mysql驱动包的问题及解决方法
2022/01/22 MySQL
sql查询语句之平均分、最高最低分及排序语句
2022/05/30 MySQL
码云(gitee)通过git自动同步到阿里云服务器
2022/12/24 Servers