MyBatis动态SQL精要:从<if>到<foreach>的灵活拼接之道


一、动态SQL:MyBatis的核心利器

动态SQL是MyBatis的核心特性之一,可以根据不同条件灵活地拼接SQL语句,

详细信息参考官方文档:动态 SQL_MyBatis中文网

二、核心动态标签详解

2.1、< if >标签

2.1.1、应用场景

在前面的教程中,我们已经介绍了基于注解和XML两种MyBatis开发方式。但从示例代码可以看出,SQL语句中的参数都是固定必传的。实际业务场景中,用户输入往往包含必填项和选填项。那么,如何实现下图所示的动态参数传递功能呢?

此时就需要使用动态标签进行判断,例如在添加操作中,将性别(gender)设为非必填字段,具体实现如下:

XML 复制代码
    <insert id="insertUserInfo2">
        insert into user_info (username, password, age,
        <if test="gender!=null">
            gender,
        </if>
        phone)
        values (#{username},#{password},#{age},
        <if test="gender!=null">
            #{gender},
        </if>
        #{phone})
    </insert>
java 复制代码
//测试处
    @Test
    void insertUserInfo2() {
        UserInfo userInfo=new UserInfo();
        userInfo.setId(11);
        userInfo.setUsername("懒羊羊");
        userInfo.setPassword("123456");
        userInfo.setAge(12);
        userInfo.setPhone("19999999999");
        userInfoMapperXML.insertUserInfo2(userInfo);
    }
---------------------------------------------------
//接口处
   Integer insertUserInfo2(UserInfo userInfo);

观察控制台运行结果:

2.1.2、< if > 的陷阱

接下来我们再来看一种情况,也就是用户也不填写 phone ,我们来实现这种情况:

XML 复制代码
    <insert id="insertUserInfoWithoutGenderOrPhone">
        insert into user_info (username, password, age,
        <if test="gender!=null">
            gender,
        </if>
        <if test="phone!=null">
            phone
        </if>                       
        )
        values (#{username},#{password},#{age},
        <if test="gender!=null">
            #{gender},
        </if>
        <if test="phone!=null">
            #{phone}
        </if>
        )
    </insert>
java 复制代码
    @Test
    void insertUserInfoWithoutGenderOrPhone() {
        UserInfo userInfo=new UserInfo();
        userInfo.setId(13);
        userInfo.setUsername("小灰灰");
        userInfo.setPassword("123456");
        userInfo.setAge(10);
        userInfoMapperXML.insertUserInfoWithoutGenderOrPhone(userInfo);
    }

观察控制台运行结果:

那既然后面会多一个逗号,那么我们不妨修改一下< if > 标签内容:

那么我们就成功解决了上述情况,但是如果我们第一个参数也是非必填字段呢?那么是不是()前方会多一个逗号,显然是这样的,如此以来,我们这种方法就行不通了,有没有更好的解决方法呢?有的,兄弟有的,如下:

2.2、< trim >标签

2.2.1、属性解析

之前的用户插入功能中,仅有一个gender 字段是可选填项。若需处理多个可选字段,通常建议采用标签化设计,通过动态生成的方式统一管理多个字段

• prefix:指定整个语句块的前缀内容

• suffix:指定整个语句块的后缀内容
• prefixOverrides:指定需要移除的语句块前缀
• suffixOverrides:指定需要移除的语句块后缀

如下代码:我们故意多一个逗号来测试:

XML 复制代码
     <insert id="insertUserInfoUseTrim">
        insert into user_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username!=null">
                username,
            </if>
            <if test="password!=null">
                password,
            </if>
            <if test="age!=null">
                age,
            </if>
            <if test="gender!=null">
                gender,
            </if>
            <if test="phone!=null">
                phone,
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username!=null">
                #{username},
            </if>
            <if test="password!=null">
                #{password},
            </if>
            <if test="age!=null">
                #{age},
            </if>
            <if test="gender!=null">
                #{gender},
            </if>
            <if test="phone!=null">
                #{phone},
            </if>
        </trim>
    </insert>
java 复制代码
    @Test
    void insertUserInfoUseTrim() {
        UserInfo userInfo=new UserInfo();
        userInfo.setUsername("红太狼");
        userInfo.setPassword("999999");
        userInfo.setAge(25);
        userInfoMapperXML.insertUserInfoUseTrim(userInfo);
    }

我们仅传入三个参数,观察控制台运行结果:

2.3、< where >标签

2.3.1、硬编码条件查询

大家先观察如下图片:

当我们在筛选商品时,可以通过设置符合需求的条件来实现过滤功能,这与数据库中的WHERE条件查询功能类似,在传统数据库操作中,我们通常采用硬编码SQL语句的方式来实现条件查询:

2.3.2、动态SQL查询

如果使用动态SQL,我们就可以指定我们需要筛选的条件 :

XML 复制代码
    <select id="selectByCondition" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select * from user_info
        where
        <trim prefixOverrides="and">
            <if test="age!=null">
                age=#{age}
            </if>
            <if test="gender!=null">
                and gender=#{gender}
            </if>
        </trim>
    </select>

但是需要处理一个问题:如果只传递 gender 参数,SQL 语句会多出一个 and 关键字,我们通过去掉前缀来删除这个 and,那么当不进行任何筛选时,SQL 语句会不会变成:

select * from user_info where 显然这种情况是必然的,针对此情况我们可以使用 where标签来解决

XML 复制代码
    <select id="selectByCondition" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select * from user_info
        <where>
            <if test="age!=null">
                and age=#{age}
            </if>
            <if test="gender!=null">
                and gender=#{gender}
            </if>
        </where>
    </select>

< where >标签:

(1)当where语句块中没有内容时,会去除 where 关键字

(2)当where语句块有查询条件时,会添加where关键字,去除最前面的 and 关键字

除标签之外,还有一个小技巧:添加 " 1 = 1 "

XML 复制代码
    <select id="selectByCondition" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select * from user_info
        where 1= 1
        <trim suffixOverrides="and">
            <if test= "age!=null">
                and age=#{age}
            </if>
            <if test= "gender!=null">
                and gender=#{gender}
            </if>
        </trim>
    </select>

2.4、< set >标签

根据用户对象属性更新数据时,可通过该标签指定动态内容

XML 复制代码
    <update id="updateBySet">
        update user_info
        <set>
            <if test="age!=null">
                age=#{age}
            </if>
            <if test="gender!=null">
                , gender=#{gender}
            </if>
            <if test="phone!=null">
                , phone=#{phone}
            </if>
        </set>
        where id = #{id}
    </update>

观察user_info表:可见实现了动态修改指定内容

<set> :在SQL语句中动态插入 set 关键字,并自动去除多余的逗号(适用于update语句)

2.5、< foreach >标签

XML 复制代码
    <select id="selectByAge" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select * from user_info where age in
        <foreach collection="ages" open="(" close=")" separator="," item="age">
            #{age}
        </foreach>
    </select>
java 复制代码
    @Test
    void selectByAge() {
        List<Integer> ages=List.of(18,19,20);
        List<UserInfo> userInfos = userInfoMapperXML.selectByAge(ages);
        userInfos.stream().forEach(x->System.out.println(x));
    }

拼接后的SQL为: select * from user_info where age in (18, 19, 20)

案例二: select * from user_info where id=10 or id=11 or id=12

java 复制代码
    <select id="selectById" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select * from user_info where
        <foreach collection="ids" separator="or" item="id">
            id=#{id}
        </foreach>
    </select>

上述SQL的分隔符为 " or",下面让我们观察查询结果:

2.6、< include > 标签

XML 映射文件中配置SQL 语句时,经常会出现大量重复代码片段,导致代码冗余问题,我们可以将重复的代码片段提取出来,使用 < sql > 标签封装成SQL片段,再通过 < include > 标签来引用这些片段,注意此处是个代码片段,不一定是完整的SQL语句

XML 复制代码
    <sql id="sql">
        select * from user_info
    </sql>
    <select id="selectAll" resultType="org.aokey.mybatisdemo.model.UserInfo">
        <include refid="sql"></include>
    </select>
  • < sql >:定义可重用的SQL片段
  • < include >:通过 refid 属性引用SQL片段
XML 复制代码
    <sql id="target">
        id, username,`password`, age, gender, phone
    </sql>
    <select id="selectByName" resultType="org.aokey.mybatisdemo.model.UserInfo">
        select
        <include refid="target"></include>
        from user_info where username = '${username}'
    </select>
相关推荐
AA-代码批发V哥9 小时前
MyBatisPlus之CRUD接口(IService与BaseMapper)
mybatis
_码农1213812 小时前
spring boot 使用mybatis简单连接数据库+连表查询
数据库·spring boot·mybatis
大得36915 小时前
django的数据库原生操作sql
数据库·sql·django
tuokuac15 小时前
SQL中的HAVING用法
数据库·sql
jnrjian15 小时前
利用trigger对大表在线同步 UDI
数据库·sql
qq_1657060717 小时前
java实现运行SQL脚本完成数据迁移
java·sql
我科绝伦(Huanhuan Zhou)19 小时前
【故障案例】Redis缓存三大难题:雪崩、击穿、穿透解析与实战解决方案
redis·缓存·mybatis
喜欢敲代码的程序员20 小时前
SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:日志管理(四)集成Spring Security
spring boot·mysql·spring·vue·mybatis
Fireworkitte21 小时前
SQL 中 CASE WHEN 及 SELECT CASE WHEN 的用法
数据库·sql·mysql