MyBatis 之 动态 SQL

MyBatis 之 动态 SQL

  • 引言
  • 传统 SQL 与 动态 SQL 之间的区别
  • 1. if 标签
    • 主要作用
    • 代码实现
    • 总结 if 标签语法
  • 2. trim 标签
    • 主要作用
    • 代码实现
    • 代码分析
    • 总结 trim 标签语法
  • 3. where 标签
    • 主要作用
    • 代码实现
    • 总结 where 标签语法
  • 4. set 标签
    • 主要作用
    • 代码实现
    • 总结 set 标签语法
  • 5. foreach 标签
    • 主要作用
    • 代码实现
    • 总结 foreach 标签语法
  • 总结动态 SQL

引言

动态 SQL 主要是用来完成不同条件下的 SQL 拼接的,它可以通过一些例如 if 标签、where 标签等,来直接限制 SQL 语句的条件。

如果说 SQL 是 HTML 代码,那么动态 SQL 的标签就相当于 JS,它可以对 SQL 语句进行变换、更改、限制。

传统 SQL 与 动态 SQL 之间的区别

我们观察下面的 userinfo 表,发现 username 和 password 是必传参数,而 photo 是有默认值的。

MyBatis 之 动态 SQL_第1张图片

所以,我们预期实现下面的两个测试。

第一个测试:插入 username、password、photo
带一个测试:插入 username 、password

那么,我们预期的第二个测试的 photo 的字段值为默认的 " 123.png ",然而,第一个测试就是我们自己插入而设置的 photo 值。

很显然,如果我们利用 MyBatis 写 SQL 语句,应该如下写:

-- 第一个测试
insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})

-- 第二个测试
insert into userinfo (username, password) values (#{username}, #{password})

基于上述的过程,我们发现,在传统的 SQL 语句写法上,针对于两次测试,我们必须写两个 SQL 语句。这就带来一个问题,如果客户端需要改变需求了,那么后端程序员就要重写写 SQL,也就要重新改变 xml 文件中的一些配置,这会很麻烦…

但实际上,如果我们利用 if 标签,就能够直接对 photo 这个字段进行限制,从而只需要写一个 SQL 语句即可。根据一个 SQL 语句,再加上一些限制标签,就能够很好地解决相似的问题。

综上所述,我们利用 MyBatis 为我们提供的标签来实现动态 SQL,会带来一个灵活的功能:后端在传 SQL 参数时,能够方便地控制增删改查的条件了。

1. if 标签

主要作用

if 标签常用来判断一个参数是否被需要,如果参数不需要,就会隐藏 SQL.

代码实现

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

" UserMapper " 接口:

@Mapper
public interface UserMapper {

    // 添加新用户,使用动态 SQL 中的 if 标签
    public int addUser2(UserInfo userInfo);
    
}

xml 文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    
    
    <insert id="addUser2">
        insert into userinfo (username, password
        <if test="photo != null">
            ,photo
        if>
        )values (#{username}, #{password}
        <if test="photo != null">
            ,#{photo}
        if>
        )
    insert>

mapper>

测试类1:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 添加新用户,使用动态 SQL 中的 if 标签
    @Test
    void addUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("JJ");
        userInfo.setPassword("246");
        userInfo.setPhoto("JJ.png");

        int result = userMapper.addUser2(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第2张图片

观察数据库:

MyBatis 之 动态 SQL_第3张图片

测试类2:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 添加新用户,使用动态 SQL 中的 if 标签
    @Test
    void addUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("云云");
        userInfo.setPassword("137");

        int result = userMapper.addUser2(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第4张图片

观察数据库:

MyBatis 之 动态 SQL_第5张图片

总结 if 标签语法

if 标签中的 test 是必传参数,传的是对象的属性。

<if test="">
   
</if>

如下图所示,if 标签中的值既可以写表的字段,也可以写对象的属性,通常情况下,这两者就是用来相互配合的,我们应该弄清楚两者的区别。

此外,如果真的分不清楚两者的关系,我们就可以将实体类的成员变量和数据表的字段写成一样的名字,这样就可以无差别对待了。

MyBatis 之 动态 SQL_第6张图片

2. trim 标签

主要作用

trim 标签一般和 if 标签搭配使用,它能够为 SQL 语句添加或去除某个字符(串)。

代码实现

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

" UserMapper " 接口:

@Mapper
public interface UserMapper {

    // 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
    public int addUser3(UserInfo userInfo);
    
}

xml 文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    
    
    <insert id="addUser3">
        insert into userinfo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null">
                username,
            if>
            <if test="password != null">
                password,
            if>
            <if test="photo != null">
                photo
            if>
        trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null">
                #{username},
            if>
            <if test="password != null">
                #{password},
            if>
            <if test="photo != null">
                #{photo}
            if>
        trim>
    insert>

mapper>

测试类1:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
    @Test
    void addUser3() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("莉莉");
        userInfo.setPassword("632");

        int result = userMapper.addUser3(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第7张图片

观察数据库:

MyBatis 之 动态 SQL_第8张图片

测试类2:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
    @Test
    void addUser3() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("杰杰");
        userInfo.setPassword("168");
        userInfo.setPhoto("杰杰.png");

        int result = userMapper.addUser3(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第9张图片

观察数据库:

MyBatis 之 动态 SQL_第10张图片

代码分析

测试类1 的 SQL 转换,即去除 " 逗号 " 的过程,如下图所示:

MyBatis 之 动态 SQL_第11张图片

此外,trim 标签非常智能,它能够自动检测 SQL 语句块的末尾是否真的有 " 逗号 ",例如我们上面的测试类2 中,我们实现了 【 suffixOverrides = “,” 】,但 photo 的末尾是没有 " 逗号 "的,也不会报错。如下所示:

insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})

总结 trim 标签语法

trim 标签有四个属性,它们可以达到往 SQL 语句块中拼接或删除字符(串)的作用,从而控制 SQL 的写法。四个属性不是必须的,可以根据语法自行设置。

① prefix:需要拼接的前缀字符串
② suffix:需要拼接的后缀字符串
③ prefixOverrides:需要删除的前缀字符串
④ suffixOverrides:需要删除的后缀字符串

3. where 标签

主要作用

where 标签一般与 if 标签搭配使用,它可以直接代替 SQL 语句中的 where 关键字,在 where 标签中,我们可以控制条件的数量,从而达到不同的 select 查询要求。

代码实现

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

" UserMapper " 接口:

@Mapper
public interface UserMapper {

    // 查询用户使用 where 标签
    public List<UserInfo> getUserById2(@Param("id") Integer id);
    
}

xml 文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    
    
    <select id="getUserById2" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
        <where>
            <if test="id != null">
                id = #{id}
            if>
        where>
    select>


mapper>

测试类1:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 查询用户使用 where 标签
    @Test
    void getUserById2() {
        List<UserInfo> list = userMapper.getUserById2(2);
        System.out.println("测试结果:" + list);
    }

}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第12张图片

观察数据库:

MyBatis 之 动态 SQL_第13张图片

测试类2:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 查询用户使用 where 标签
    @Test
    void getUserById2() {
        List<UserInfo> list = userMapper.getUserById2(null);
        System.out.println("测试结果:" + list);
    }

}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第14张图片

观察数据库:

MyBatis 之 动态 SQL_第15张图片

总结 where 标签语法

如果 where 标签中的有值,那么就会正常执行 where 后面的 SQL 语句,如果 where 标签中没有值,那么整个 where 语句也会被省略。所以一般情况下,where 被省略了,就会变成一个全列查询。

此外,where 标签也等价于下面的写法,实际上它能省略语句中的前缀 and,但是它省略不了语句中的后缀 and.

<trim prefix="where" prefixOverrides="and">
    
trim>

基于上述的 where 语法,形如下面的代码,一个 SQL 语句中,我们就可以变幻出四种语法。

select * from userinfo
<where>
    <if test="username!= null">
        username= #{usernname}
    if>
    <if test="password!= null">
        and password= #{password}
    if>
where>

① 只查询 username
② 只查询 password
③ 既查询 username,也查询 password
④ 全列查询

4. set 标签

主要作用

set 标签一般与 if 标签搭配使用,它可以直接代替 SQL语句中的 set 关键字,在 set 标签中,我们可以控制修改字段的数量,从而达到不同的 update 修改要求。

代码实现

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

" UserMapper " 接口:

@Mapper
public interface UserMapper {

    // 修改用户使用 set 标签
    public int update2(UserInfo userInfo);
    
}

xml 文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    
    
    <update id="update2">
        update userinfo
        <set>
            <if test="username != null">
                username = #{username},
            if>
            <if test="password != null">
                password = #{password},
            if>
            <if test="photo != null">
                photo = #{photo}
            if>
        set>
        where id = #{id}
    update>

mapper>

测试类1:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 修改用户使用 set 标签
    @Test
    void update2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("王五");
        userInfo.setPassword("777");
        userInfo.setPhoto("777.png");
        userInfo.setId(7);

        int result = userMapper.update2(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第16张图片

观察数据库:

MyBatis 之 动态 SQL_第17张图片

测试类2:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 修改用户使用 set 标签
    @Test
    void update2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("王五");
        userInfo.setPassword("777");
        userInfo.setPhoto("777.png");
        userInfo.setId(7);

        int result = userMapper.update2(userInfo);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第18张图片

观察数据库:

MyBatis 之 动态 SQL_第19张图片

总结 set 标签语法

一般来说,set 用于 update 修改操作中,所以,既然要修改了,那么在 set 标签中,必然是需要至少一个值,否则就会报错。

此外,set 标签也等价于下面的写法,实际上它能省略语句中的后缀 " 逗号 ",但是它省略不了语句中的前缀 " 逗号 " .

<trim prefix="set" suffixOverrides=",">

trim>

此外,它也很智能,它能够自动检测 SQL 语句块的末尾是否真的有 " 逗号 ",对于末尾没有逗号的情况,它不会执行删除操作,同时也不会报错。

5. foreach 标签

主要作用

foreach 标签一般与 SQL 中的 in() 关键字搭配,可以做到批量化操作。

下面的代码就是展示了批量删除的操作。

代码实现

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

" UserMapper " 接口:

@Mapper
public interface UserMapper {

    // 批量删除使用 foreach 标签
    public int deleteByIds(List<Integer> ids);
    
}

xml 文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    
    
    <delete id="deleteByIds">
        delete from userinfo where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        foreach>
    delete>

mapper>

测试类1:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    // 批量删除使用 foreach 标签
    @Test
    void deleteByIds() {
        List<Integer> ids = new ArrayList<>();
        // 添加需要删除的用户 id
        ids.add(8);
        ids.add(9);
        ids.add(10);

        int result = userMapper.deleteByIds(ids);
        System.out.println("测试结果: " + result);
    }
}

启动测试方法,查看 MyBatis 日志打印:

MyBatis 之 动态 SQL_第20张图片

观察数据库:

MyBatis 之 动态 SQL_第21张图片

总结 foreach 标签语法

foreach 标签的语法可以类比于 Java 中 for each 遍历数组的用法。

foreach 标签常用的有五个属性,它们可以达到往 SQL 语句块中拼接或删除字符(串)的作用,从而控制 SQL 的写法。

① collection:一组相同的数据的集合,类似于数组,
这里需要注意,collection 中的值需要与前端传来的参数匹配。

② item:集合中的一项数据,类似于数组中的某一个元素
③ open:需要拼接的前缀字符(串)
④ close:需要拼接的后缀字符(串)
⑤ separator:一组数据的分隔符

总结动态 SQL

动态 SQL 就和它的名字一样,赋予了 SQL 一个动态的语法,我们可以根据前端需要的参数,来控制一整条 SQL 语句。本质上,关于动态 SQL 的标签,就是用来拼接或删除 SQL 的,没有其他的作用。

在使用动态 SQL 之前,我们应该先把心中设想好的完完整整的 SQL 写出来,之后再对此 SQL 进行变换,这样一来,便不会出错。

你可能感兴趣的:(Spring,系列,mybatis,sql,java)