MyBatis 之 动态 SQL

引言

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

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

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

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

1-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 文件

<?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="com.example.demo.mapper.UserMapper">

    <!-- 添加新用户,使用动态 SQL 中的 if 标签 -->
    <!-- insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})  -->
    <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 日志打印:

1-2

观察数据库:

1-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 日志打印:

1-4

观察数据库:

1-5

总结 if 标签语法

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

<if test="">
   
</if>

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

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

1-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 文件

<?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="com.example.demo.mapper.UserMapper">

    <!--  添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签 -->
    <!-- insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})  -->
    <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 日志打印:

1-7

观察数据库:

1-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 日志打印:

1-9

观察数据库:

1-10

代码分析

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

2-1

此外,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 文件

<?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="com.example.demo.mapper.UserMapper">

    <!--  根据用户 id 来查询某个用户的所有信息 -->
    <!-- select * from userinfo where id = #{id} -->
    <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 日志打印:

2-2

观察数据库:

2-3

测试类2:

@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

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

}

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

2-4

观察数据库:

2-5

总结 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 文件

<?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="com.example.demo.mapper.UserMapper">

    <!-- 修改用户使用 set 标签 -->
    <!--  update userinfo set username = #{username} where id = #{id} -->
    <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 日志打印:

2-6

观察数据库:

2-7

测试类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 日志打印:

2-8

观察数据库:

2-9

总结 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 文件

<?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="com.example.demo.mapper.UserMapper">

    <!-- 批量删除使用 foreach 标签 -->
    <!-- delete from userinfo where id in ( , , ,); -->
    <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 日志打印:

3-1

观察数据库:

3-2

总结 foreach 标签语法

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

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

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

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

总结动态 SQL

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

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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis是一个流行的Java持久层框架,它允许开发者在运行时构建动态SQL查询,从而提高了代码的灵活性和适应性。MyBatis通过结合XML映射文件和注解,实现了动态SQL的执行。以下是MyBatis动态SQL的主要实现方式: 1. XML映射文件(Mapper XML):在MyBatis中,`<select>`, `<update>`, `<delete>`等标签可以包含参数占位符,如`#{id}`, `#{name}`,这些占位符会在运行时被实际的值替换,形成动态SQL语句。 ```xml <select id="getUser" parameterType="int" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> ``` 2. 参数化查询(Parametrized queries):MyBatis支持使用预编译语句,将参数与SQL语句分离,这样可以防止SQL注入攻击。 3. 显式对象(Parameter Objects):如果动态SQL非常复杂,可以创建一个Java对象作为参数传递给查询,其中包含了多个属性,MyBatis会自动将对象的属性转换为SQL中的列名。 ```java Map<String, Object> params = new HashMap<>(); params.put("startDate", startDate); params.put("endDate", endDate); List<User> users = sqlSession.selectList("getUsers", params); ``` 4. 动态SQL标签:MyBatis提供了`<if>`, `<choose>`, `<when>`, `<otherwise>`等标签,用于根据条件动态生成SQL,实现基于条件的分支查询。 ```xml <select id="getUser" parameterType="map" resultType="User"> <if test="id != null"> SELECT * FROM users WHERE id = #{id} </if> <if test="name != null"> AND name LIKE '%' + #{name} + '%' </if> </select> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十七ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值