# MyBatis教程 - 10 动态SQL

动态 SQL 是 MyBatis 中的一个强大特性,它允许在编写 SQL 语句时,根据不同的条件动态地改变 SQL 语句的结构。

例如在订单页面,可能会根据订单号、用户名、日期来查询订单,我们可以根据输入哪个条件来将这个条件动态的加入到 SQL 中,这就需要条件判断来动态的改变 SQL 语句。

MyBatis 提供了多个 XML 标签来支持动态 SQL,如 <if><choose><when><otherwise>)、<foreach><where><set> 等。下面详细介绍一下这些标签。

# 10.1 if条件判断

<if> 标签可以根据不同的条件来查询信息。

举个栗子:

UserMapper.xml 中定义 SQL 映射,如下:

<select id="findByConditions" resultType="User">
    SELECT * FROM tb_user
    WHERE 1=1
    <if test="username != null and username != ''">
        AND username = #{username}
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13

上面使用了 <if> 标签,判断参数是否为空,当不为空的时候,才拼接 SQL。使用了 1=1 这个永远成立的条件,是为了当条件不满足,没有拼接 SQL 的时候,SQL 也是正确的命令。

针对上面的映射,只需要传递 usernameemailage 参数。

所以下面三种方式定义的接口都是可以的:

UserMapper.java :

/**
 * 根据条件查询用户
 */
List<User> findByConditions(User user);

/**
 * 根据条件查询用户
 */
List<User> findByConditions(Map<String, Object> map);

/**
 * 根据条件查询用户
 */
List<User> findByConditions(@Param("username") String username,  @Param("email") String email, @Param("age") Integer age);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

上面三种方式定义的接口,都可以映射到 xml 中的 SQL。

# 10.2 where标签

在上面使用 <if> 标签拼接条件,需要额外使用 1=1 来处理 WHERE,这样即使所有的查询条件为空,也会有 WHERE 条件判断。

其实我们可以使用 <where> 标签,它可以在生成 SQL 语句时智能地处理 WHERE 条件部分。

<where> 标签的作用:

  • 自动添加 WHERE 子句:当 SQL 中有条件时,<where> 标签会自动添加 WHERE 子句。
  • 自动去除多余的 ANDOR:在动态生成的 SQL 中,MyBatis 会自动去除开头的(后面的去不掉) ANDOR,避免 SQL 语法错误。

举个栗子,重新写一个多条件查询用户的例子:

<select id="findByConditions" resultType="User">
    SELECT * FROM tb_user
    <where>
        <if test="username != null and username!= ''">
            username = #{username}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="age!= null">
            AND age = #{age}
        </if>
    </where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过使用 <where> 标签将条件包裹,这样如果条件都不满足,那么 SQL 语句是不会有 WHERE 条件子句的。如果 username 条件为空,后面 SQL 的拼接也不会有问题,MyBatis 会自动去掉前面的(后面的去不掉) ANDOR

# 10.3 choose选择

<choose><when><otherwise> 也是实现条件判断的动态 SQL 标签。这些标签类似于 Java 中的 switch-case 语句,允许你在 SQL 中根据不同的条件生成不同的 SQL 片段,从而实现复杂的逻辑判断。

使用 <choose> 标签包裹 <when><otherwise> 标签,<when> 标签相当于 switch-case 语句中的每个 case<otherwise> 标签相当 switch-case 语句中的 default


举个栗子:

<select id="findByConditions" resultType="User">
    SELECT * FROM tb_user
    <where>
        <!-- 使用choose -->
        <choose>
            <when test="id != null and id != ''">
                id = #{id}
            </when>
            <when test="username != null and username != ''">
                username = #{username}
            </when>
            <otherwise>
                email = #{email}
            </otherwise>
        </choose>
    </where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上面的 idusernameemail 条件只会使用一个,和 switch-case 语句一样。

在执行的时候,通过日志可以看到当前执行的 SQL 语句:

上面是没有传递ID,传递了username和age的情况下,执行的SQL。

# 10.4 foreach循环

<foreach> 标签是 MyBatis 中用于处理集合数组的动态 SQL 元素。它可以帮助你在 SQL 语句中批量处理多个参数,例如生成 IN 子句、批量插入等操作。

# 1 批量删除

先实现一个根据 ID 数组,批量删除用户的功能。

定义接口方法,UserMapper.java

/**
 * 批量删除用户
 */
int deleteByIds(@Param("ids") Integer[] ids);
1
2
3
4

接口需要使用命名参数 @param ,否则参数的名称为 arrayarg0


定义SQL映射,UserMapper.xml

<delete id="deleteByIds">
    DELETE FROM tb_user WHERE id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>
1
2
3
4
5
6

使用 foreach 标签,循环遍历数组或集合,最终生成 SQL 中 in 部分的 (id1, id2, id3) 格式。open="(" 表示开始的 (close=")" 表示结束的 )

其中:

  • collection:指定要遍历的集合或数组,可以是 ListSet、数组等。

  • item:表示在遍历时每次迭代的元素,在循环内部可以使用这个名称来引用当前元素。

  • separator:指定在每次迭代生成的 SQL 片段之间添加的分隔符,如逗号、AND、OR 等。

  • open:指定生成的 SQL 片段的起始部分,通常用于包裹结果(如括号)。

  • close:指定生成的 SQL 片段的结束部分,通常用于包裹结果(如括号)。

  • index:表示当前迭代的索引,通常用于调试或需要索引值的场景。

上面使用的是 id in (id1, id2, id3) 这样的格式,还可以使用 id = id1 OR id = id 2 OR id = id3 这样的格式,那么 SQL 映射可以这样写:

<delete id="deleteByIds">
    DELETE FROM tb_user WHERE
    <foreach collection="ids" item="id" separator="or">
        id = #{id}
    </foreach>
</delete>
1
2
3
4
5
6

上面的SQL映射,使用 or 进行元素的连接。生成的 SQL 为:DELETE FROM tb_user WHERE id = ? or id = ? or id = ?

# 2 批量添加

首先定义接口方法,UserMapper.java

/**
 * 批量插入用户
 */
int insert(@Param("userList") List<User> userList);
1
2
3
4

接口需要使用命名参数 @param ,否则参数的名称为 arrayarg0


定义SQL映射,UserMapper.xml

<insert id="insert">
    INSERT INTO tb_user (`id`, `username`, `email`, `age`, `create_time`, `update_time`) VALUES
    <foreach collection="userList" item="user" separator=",">
        (null, #{user.username}, #{user.email}, #{user.age}, NOW(), NOW())
    </foreach>
</insert>
1
2
3
4
5
6

通过 <foreach> 拼接成多个插入的值 ,格式是:(),(),()

id 是数据库自增的,所以赋值为空。


编写测试方法:

@Test
public void testInsertUser() {
    // 获取SqlSession连接
    SqlSession sqlSession = MyBatisUtils.getSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList = new ArrayList<>();
    userList.add(new User("zhangsan", "zhangsan@foooor.com", 13));  // 这里定义了构造方法
    userList.add(new User("doubi", "doubi@foooor.com", 14));
    userList.add(new User("foooor", "foooor@foooor.com", 15));

    // 删除用户
    int result = userMapper.insert(userList);
    log.info("result: {}", result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 10.5 set标签

<set> 标签是 MyBatis 提供的一种动态 SQL 元素,主要用于生成 UPDATE 语句中的 SET 子句。它能够智能地处理多个条件的动态拼接,并自动去除多余的逗号,确保生成的 SQL 语法正确。

<set> 标签的作用:

  • 动态生成 SET 子句<set> 标签能够根据条件动态生成 SQL 的 SET 子句。

  • 自动处理逗号<set> 会自动去除每个条件后面多余的逗号,确保 SQL 语法正确。

举个栗子:

定义 SQL 映射,UserMapper.xml

<update id="update">
    UPDATE tb_user
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="email != null and email != ''">
            email = #{email},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </set>
    WHERE id = #{id}
</update>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<set> 标签中可以使用条件判断,设置更新的值。


定义更新的接口,以下几种方式都可以对应上面的 SQL 映射。

UserMapper.java

/**
 * 根据条件查询用户
 */
int update(User user);

/**
 * 根据条件查询用户
 */
int update(Map<String, Object> map);

/**
 * 根据条件查询用户
 */
int update(@Param("id") Integer id, @Param("username") String username,  @Param("email") String email, @Param("age") Integer age);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后调用接口就可以了。

# 10.6 sql标签片段

在前面查询的时候,我们都是使用了 SELECT * ,但这不是一个很好的方式,一般都是需要什么字段查询什么字段。

例如:

SELECT id, username, password, age
1

但是又会有一个问题就是我们的查询方法可能会有很多,所以就需要在很多地方写 SELECT id, username, password, age


所以我们可以使用 <sql> 标签片段,定义一个 sql 片段,然后在使用的地方进行引用,增加代码复用,减少重复代码。

举个栗子:

<sql id="userColumns">
    id, username, email, age
</sql>

<select id="selectById" resultType="User">
    SELECT <include refid="userColumns"></include>
    FROM tb_user
    WHERE id = #{id}
</select>
1
2
3
4
5
6
7
8
9

使用 <sql> 标签定义 sql 片段,然后在使用的地方使用 <include> 标签进行引用。