# 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>
2
3
4
5
6
7
8
9
10
11
12
13
上面使用了 <if>
标签,判断参数是否为空,当不为空的时候,才拼接 SQL。使用了 1=1
这个永远成立的条件,是为了当条件不满足,没有拼接 SQL 的时候,SQL 也是正确的命令。
针对上面的映射,只需要传递 username
、email
、 age
参数。
所以下面三种方式定义的接口都是可以的:
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);
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
子句。 - 自动去除多余的
AND
或OR
:在动态生成的 SQL 中,MyBatis 会自动去除开头的(后面的去不掉)AND
或OR
,避免 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
通过使用 <where>
标签将条件包裹,这样如果条件都不满足,那么 SQL 语句是不会有 WHERE
条件子句的。如果 username
条件为空,后面 SQL 的拼接也不会有问题,MyBatis 会自动去掉前面的(后面的去不掉) AND
或 OR
。
# 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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面的 id
、username
、email
条件只会使用一个,和 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);
2
3
4
接口需要使用命名参数 @param
,否则参数的名称为 array
或 arg0
。
定义SQL映射,UserMapper.xml
:
<delete id="deleteByIds">
DELETE FROM tb_user WHERE id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
2
3
4
5
6
使用 foreach
标签,循环遍历数组或集合,最终生成 SQL 中 in
部分的 (id1, id2, id3)
格式。open="("
表示开始的 (
,close=")"
表示结束的 )
。
其中:
collection
:指定要遍历的集合或数组,可以是List
、Set
、数组等。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>
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);
2
3
4
接口需要使用命名参数 @param
,否则参数的名称为 array
或 arg0
。
定义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>
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);
}
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>
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
然后调用接口就可以了。
# 10.6 sql标签片段
在前面查询的时候,我们都是使用了 SELECT *
,但这不是一个很好的方式,一般都是需要什么字段查询什么字段。
例如:
SELECT id, username, password, age
但是又会有一个问题就是我们的查询方法可能会有很多,所以就需要在很多地方写 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>
2
3
4
5
6
7
8
9
使用 <sql>
标签定义 sql 片段,然后在使用的地方使用 <include>
标签进行引用。