一、应用场景
MyBatis 的 动态 SQL 是指根据不同的条件动态拼接生成 SQL 语句的能力。它的最大优势是:避免写多个 XML 映射语句、避免 SQL 冗余、提升代码复用性和可维护性。
示例1:用户可以通过勾选框,勾选不同的数据进行批量删除,此时:
delete from xxx_table where id in (a, b, c, d, ......)
in后面括号中的数据就是动态的。
示例2:用户搜索功能,查询条件不确定(常用于搜索/筛选)
二、使用方式(XML + 标签)
MyBatis 动态 SQL 主要通过 XML 配置文件中的标签 来实现,核心标签如下:
标签 | 作用 |
---|---|
<if> | 条件判断 |
<choose> / <when> / <otherwise> | 类似 Java 中的 if-else if-else |
<where> | 自动加上 WHERE 并避免多余的 AND/OR |
<set> | 用于 UPDATE ,自动去除最后的逗号 |
<foreach> | 用于遍历集合(常见于 IN、批量插入) |
<trim> | 自定义前缀、后缀、去掉前后符号等 |
2-1、<if>标签
示例:
List<Car> selectByMutiConditions(@Param("brand") String brand,@Param("guidePrice") Double guidePrice,@Param("carType") String carType);
<select id="selectByMutiConditions" resultType="car">select * from t_car where 1=1/*1. if标签中,test属性是必须的,值:表达式(false/true)2. test属性可以使用的是:使用了@Param标签,就用@Param标签指定的参数名;没有使用@Param标签,就用:param1, param2, param3, arg0, arg1, arg2...当使用了pojo,就用pojo中的属性名3. 在mybatis的动态sql中,不能使用&&,只能使用and*/<if test="brand != null and brand != ''">and brand like "%"#{brand}"%"</if><if test="guidePrice != null and guidePrice != ''">and guidePrice > #{guidePrice}</if><if test="carType != null and carType != ''">and carType = #{carType}</if></select>
【注意点】:
1. if标签中,test属性是必须的,值:表达式(false/true)
2. test属性可以使用的是:
- 使用了@Param标签,就用@Param标签指定的参数名;
- 没有使用@Param标签,就用:param1, param2, param3, arg0, arg1, arg2...
- 当使用了pojo,就用pojo中的属性名
3. 在mybatis的动态sql中,不能使用&&,只能使用and。
4、为了防止有部分条件不成立的时候,where后面直接拼接了and,需要加上1=1
2-2、<where>标签
它的作用是在生成 SQL 语句时:
自动去掉首个条件前多余的
AND
或OR
当有条件时自动加上
WHERE
关键字当没有任何条件时不会生成
WHERE
子句
【注意】:
<where>
标签只对内容中出现的 第一个 AND/OR 进行清理,建议把每个<if>
条件前都加上AND
,它会自动清理第一个多余的。
示例:
2-3、<trim>标签
在 MyBatis 中,<trim>
是一个功能非常强大的 动态 SQL 标签,它可以灵活地控制:
前缀(prefix):开头加什么
后缀(suffix):结尾加什么
前缀去除(prefixOverrides):去掉开头多余的关键字,比如
AND
/OR
/,
后缀去除(suffixOverrides):去掉结尾多余的内容,比如
,
你可以把
<trim>
看成<where>
和<set>
的“底层基础版本”,功能更灵活。
1、常见属性说明
属性名 | 说明 |
---|---|
prefix | 给内容加上前缀,比如 "WHERE" 、"SET" |
suffix | 给内容加上后缀 |
prefixOverrides | 去除内容前面多余的前缀(如多余的 "AND" 、"OR" ) |
suffixOverrides | 去除内容最后多余的后缀(如多余的 "," ) |
【注意】:
它们 都是在整体 trim 内容拼接完毕后,一次性处理整个 SQL 片段的前缀和后缀!
示例 1:动态 WHERE 条件,代替 <where>
<select id="selectUser" parameterType="User" resultType="User">SELECT * FROM user<trim prefix="WHERE" prefixOverrides="AND |OR "><if test="username != null"> AND username = #{username} </if><if test="gender != null"> AND gender = #{gender} </if></trim>
</select>
效果(如果 username != null 且 gender == null):
SELECT * FROM user WHERE username = 'Tom'
自动去掉多余的 AND
,和 <where>
效果一样,但更灵活(你也可以换成 OR)。
示例 2:动态更新字段,代替 <set>
<update id="updateUser" parameterType="User">UPDATE user<trim prefix="SET" suffixOverrides=","><if test="username != null"> username = #{username}, </if><if test="gender != null"> gender = #{gender}, </if></trim>WHERE id = #{id}
</update>
效果:
如果 username = 'Tom', gender = null:
UPDATE user SET username = 'Tom' WHERE id = 1
自动去掉最后多余的逗号
,
示例 3:完全自定义的 SQL 拼接
<trim prefix="(" suffix=")" suffixOverrides=","><foreach collection="ids" item="id">#{id},</foreach>
</trim>
输出示例:
(1, 2, 3)
2-4、<set>标签
<set>
标签的作用是:
自动添加
SET
关键字自动去除多余的结尾逗号
,
与
<if>
标签组合使用,实现根据条件动态更新字段
目的:希望更新的时候,只更新不为null的字段,其余字段不动!
2、基本语法结构
<update id="updateUser" parameterType="User">UPDATE user<set><if test="username != null"> username = #{username}, </if><if test="email != null"> email = #{email}, </if><if test="age != null"> age = #{age}, </if></set>WHERE id = #{id}
</update>
3、执行逻辑过程
假设只传了 username
和 age
,email
是 null。
MyBatis 会生成这样的 SQL:
UPDATE user
SET username = ?, age = ?
WHERE id = ?
注意:
✅ 自动添加了
SET
✅ 自动移除了末尾多余的
,
4、和 <trim>
的关系
<set>
本质上就是 <trim>
的封装版本:
等价于:
<trim prefix="SET" suffixOverrides=",">...
</trim>
也就是说,如果你希望自定义更多行为(如不使用 SET
、用别的关键字),可以改用 <trim>
标签。
2-5、<choose>、<when>、<otherwise>
MyBatis 中的 <choose>
标签是用来实现 类似 Java 中 if-else if-else 结构的动态 SQL 分支选择语句,非常适合你有多个条件分支,但只想执行其中一个的情况。
1、 作用:
<choose>
是 MyBatis 提供的一个逻辑分支标签,用来表示:
如果满足第一个条件就执行它,否则判断第二个条件……都不满足则执行默认分支。
它的结构和 Java 中的 if ... else if ... else
是一致的:
if (a != null) {// ...
} else if (b != null) {// ...
} else {// default
}
在 MyBatis 中写法如下:
<choose><when test="a != null">...</when><when test="b != null">...</when><otherwise>...</otherwise>
</choose>
2、使用示例
例子:根据传入的条件查询用户
<select id="findUser" parameterType="map" resultType="User">SELECT * FROM user<where><choose><when test="id != null">id = #{id}</when><when test="username != null">username = #{username}</when><otherwise>gender = 'male'</otherwise></choose></where>
</select>
3、执行逻辑:
假设传入参数是:
Map<String, Object> param = new HashMap<>();
param.put("username", "Tom");
生成的 SQL 就是:
SELECT * FROM user WHERE username = 'Tom'
如果传入参数是:
param.put("id", 123);
param.put("username", "Tom");
优先满足第一个条件:
SELECT * FROM user WHERE id = 123
如果两个都没传,则使用 otherwise
:
SELECT * FROM user WHERE gender = 'male'
4、几点注意事项
特性 | 说明 |
---|---|
<choose> 内只能选中 一个 <when> 被执行 | 只执行第一个匹配成功的 <when> |
<otherwise> 是可选的 | 不写也可以 |
多个 <when> 的顺序很重要 | 从上往下匹配,匹配到就停 |
2-6、<foreach>标签
MyBatis 中的 <foreach>
标签是动态 SQL 中非常重要的一个标签,主要用于 遍历集合(如 List、数组、Map)生成一段重复的 SQL 语句,特别适合用于:
批量插入、批量删除、
IN (...)
条件语句
1、 的核心作用
用来遍历集合(如 List
、数组
、Map
),在 SQL 中动态生成重复片段,比如:
id IN (1, 2, 3)
多行
VALUES
插入多个字段拼接
2、常见属性说明
属性名 | 说明 |
---|---|
collection | 要遍历的集合名,如 list 、array 、map 等 |
item | 遍历过程中每一项的变量名 |
index | 当前项的下标(可选) |
open | 开始拼接的前缀(如 ( ) |
close | 拼接结束的后缀(如 ) ) |
separator | 每项之间的分隔符(如 , ) |
3、经典使用场景
(1). IN (...)
查询
<select id="selectByIds" parameterType="list" resultType="User">SELECT * FROM userWHERE id IN<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
传入:
List<Integer> ids = Arrays.asList(1, 2, 3);
生成 SQL:
SELECT * FROM user WHERE id IN (1, 2, 3)
(2). 批量插入
<insert id="insertUsers" parameterType="list">INSERT INTO user (username, age)VALUES<foreach collection="list" item="u" separator=",">(#{u.username}, #{u.age})</foreach>
</insert>
【注意】:
此时,属性:open,close都没有写!
传入:
List<User> users = List.of(new User("Tom", 20),new User("Jerry", 22)
);
生成 SQL:
INSERT INTO user (username, age)
VALUES ('Tom', 20), ('Jerry', 22)
(3). 遍历 Map,实现动态 UPDATE 字段
<foreach collection="map" item="val" index="key">${key} = #{val}
</foreach>
示例:
int updateForEach(@Param("updateFields") Map<String, Object> updateFields,@Param("id") Long id);
【注意】:
此时的key用的是${key},因为是数据库字段,不能用#{key},否则拼接的数据库字段会加上字符串!
4、常见坑点提示
问题 | 说明 |
---|---|
collection="list" | 如果参数是 List ,要写 "list" (不是写变量名) |
<foreach> 必须配合 open/close/separator | 否则 SQL 拼接可能出错 |
item=#{} 中的 item 要和声明保持一致 | 否则无法解析参数 |
避免用 ${} 插值 | 容易引发 SQL 注入问题 |
2-7、<include>标签
MyBatis 中的 <include>
标签用于 在 XML 映射文件中复用 SQL 片段,类似 Java 中的方法提取,可以提高 SQL 语句的复用性和可维护性。
1、使用场景
多个 SQL 中有相同字段、相同条件、重复的列名等
希望统一维护公共 SQL 内容,避免拷贝粘贴
2、基本语法
1. 定义可复用的 SQL 片段(使用 <sql>
标签)
<sql id="baseColumnList">id, name, age, gender, email
</sql>
2. 引用 SQL 片段(使用 <include>
标签)
<select id="selectAll" resultType="User">SELECT<include refid="baseColumnList" />FROM users
</select>
3、完整示例
<!-- 定义通用字段列表 -->
<sql id="baseColumnList">id, name, age, gender, email
</sql><!-- 使用 include 引用 -->
<select id="selectById" resultType="User">SELECT <include refid="baseColumnList" />FROM usersWHERE id = #{id}
</select>
4、refid 范围说明
<sql id="...">
的id
必须在当前命名空间中唯一如果跨命名空间使用,需要使用
namespace.id
引用,例如:
<include refid="com.example.mapper.UserMapper.baseColumnList" />
⚠️ 注意事项
不支持
<sql>
中再嵌套<sql>
或<include>
!!!