Appearance
动态 SQL
动态 SQL 是 MyBatis 最强大的特性之一,让你在 XML 中根据条件动态拼接 SQL,彻底告别 Java 代码里的字符串拼接地狱。
一、为什么需要动态 SQL?
java
// 没有动态 SQL,只能这样拼接(噩梦)
String sql = "SELECT * FROM user WHERE 1=1";
if (name != null) {
sql += " AND name LIKE '%" + name + "%'"; // SQL 注入风险!
}
if (age != null) {
sql += " AND age = " + age;
}
if (email != null) {
sql += " AND email = '" + email + "'";
}xml
<!-- 有了 MyBatis 动态 SQL -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>二、核心标签
2.1 if(条件判断)
用法:根据条件决定是否拼接 SQL 片段
test 属性中可用的表达式:
- 逻辑运算:and, or, not
- 比较运算:==, !=, >, >=, <, <=
- 属性判断:name != null, name != ''
- 集合判断:list != null and list.size() > 0xml
<select id="selectByCondition" resultType="User">
SELECT * FROM user WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</select>2.2 choose-when-otherwise(分支选择)
用法:类似 Java 的 switch-case,只选择第一个匹配的条件
结构:
<choose>
<when test="条件1"> SQL片段1 </when>
<when test="条件2"> SQL片段2 </when>
<otherwise> 默认SQL片段 </otherwise>
</choose>xml
<select id="selectByPriority" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="name != null and name != ''">
AND name = #{name}
</when>
<when test="email != null and email != ''">
AND email = #{email}
</when>
<when test="id != null">
AND id = #{id}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
</select>2.3 trim-where-set(智能去除)
where 标签
作用:替代 WHERE 1=1 写法,智能处理 AND/OR 前缀
规则:
① 如果内部有文本返回,自动加 WHERE 关键字
② 如果第一个条件是 AND 或 OR,自动去除
③ 如果内部没有文本返回,不输出 WHERExml
<!-- 使用 where 标签 -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</where>
</select>
<!-- 等价于用 trim 实现 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</trim>set 标签
作用:动态更新时智能处理逗号后缀
规则:
① 自动加 SET 关键字
② 自动去除末尾多余的逗号xml
<update id="updateSelective">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
<!-- 等价于用 trim 实现 -->
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</trim>trim 标签
作用:最灵活的动态 SQL 标签,可以添加/删除前后缀
四个属性:
prefix → 在前面添加的内容
prefixOverrides → 去掉前面的指定内容
suffix → 在后面添加的内容
suffixOverrides → 去掉后面的指定内容xml
<!-- 自定义 INSERT 语句 -->
<insert id="insertSelective">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">name,</if>
<if test="age != null">age,</if>
<if test="email != null">email,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="name != null">#{name},</if>
<if test="age != null">#{age},</if>
<if test="email != null">#{email},</if>
</trim>
</insert>2.4 foreach(遍历)
作用:遍历集合,常用于 IN 查询和批量操作
六个属性:
collection → 要遍历的集合(必填)
item → 每个元素的别名(必填)
index → 索引(可选)
open → 开始符号
close → 结束符号
separator → 分隔符xml
<!-- ========== IN 查询 ========== -->
<select id="selectByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 结果:SELECT * FROM user WHERE id IN (1, 2, 3) -->
<!-- ========== 批量插入 ========== -->
<insert id="insertBatch">
INSERT INTO user (name, age, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
<!-- 结果:INSERT INTO user (name, age, email) VALUES (?, ?, ?), (?, ?, ?) -->
<!-- ========== 批量删除 ========== -->
<delete id="deleteByIds">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>collection 属性取值:
| 参数类型 | collection 值 |
|---|---|
| List | list(默认)或 @Param 指定的名字 |
| Array | array(默认)或 @Param 指定的名字 |
| Set | collection(默认)或 @Param 指定的名字 |
| Map | _parameter 或 key 名 |
java
// 传入 List
List<User> selectByIds(@Param("ids") List<Long> ids);
// XML: collection="ids"
// 传入数组
List<User> selectByIds(Long[] ids);
// XML: collection="array"
// 传入 Map 中的 List
Map<String, Object> params = new HashMap<>();
params.put("ids", Arrays.asList(1L, 2L, 3L));
// XML: collection="ids"2.5 bind(变量绑定)
作用:创建变量,用于 SQL 中,解决更换数据库时的兼容问题xml
<!-- 模糊查询:MySQL 用 CONCAT,Oracle 用 || -->
<select id="selectByName" resultType="User">
<!-- 使用 bind 统一写法 -->
<bind name="pattern" value="'%' + name + '%'"/>
SELECT * FROM user WHERE name LIKE #{pattern}
</select>
<!-- 防止 SQL 注入的 LIKE 写法 -->
<select id="selectByNameSafe" resultType="User">
<bind name="safeName" value="'%' + name + '%'"/>
SELECT * FROM user WHERE name LIKE #{safeName}
</select>三、实战场景
3.1 多条件查询
xml
<select id="selectByMultiCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="createTimeStart != null">
AND create_time >= #{createTimeStart}
</if>
<if test="createTimeEnd != null">
AND create_time <= #{createTimeEnd}
</if>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy} ${sortDirection}
</if>
</select>3.2 批量操作
xml
<!-- 批量插入 -->
<insert id="insertBatch">
INSERT INTO user (name, age, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
<!-- 批量更新(MySQL 特有语法) -->
<update id="updateBatch">
<foreach collection="list" item="user" separator=";">
UPDATE user
SET name = #{user.name}, age = #{user.age}
WHERE id = #{user.id}
</foreach>
</update>
<!-- 批量更新(通用写法) -->
<update id="updateBatchByCase">
UPDATE user
<trim prefix="SET" suffixOverrides=",">
<trim prefix="name = CASE" suffix="END,">
<foreach collection="list" item="user">
WHEN id = #{user.id} THEN #{user.name}
</foreach>
</trim>
<trim prefix="age = CASE" suffix="END,">
<foreach collection="list" item="user">
WHEN id = #{user.id} THEN #{user.age}
</foreach>
</trim>
</trim>
WHERE id IN
<foreach collection="list" item="user" open="(" separator="," close=")">
#{user.id}
</foreach>
</update>3.3 动态排序
xml
<select id="selectByOrder" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name = #{name}</if>
</where>
<!-- 排序字段必须用 ${},因为表名/列名不能用占位符 -->
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
<if test="sortDirection != null and sortDirection != ''">
${sortDirection}
</if>
</if>
</select>注意:
${orderBy}有 SQL 注入风险,必须在 Java 代码中做白名单校验。
四、OGNL 表达式
MyBatis 动态 SQL 中的 test 属性使用 OGNL 表达式
常用语法:
- 基本:name != null, age > 18, status == 1
- 字符串:name != null and name != ''
- 集合:list != null and list.size() > 0
- 三元:sex == 1 ? '男' : '女'
- 方法调用:name.trim() != ''
- 取反:!list.isEmpty()五、速记总结
MyBatis 动态 SQL 六大标签:
if → 条件判断,满足就拼接
choose-when-otherwise → 多选一,类似 switch
where → 自动加 WHERE 并去除多余 AND/OR
set → 自动加 SET 并去除多余逗号
trim → 万能标签,自定义前后缀
foreach → 遍历集合,IN 查询和批量操作
bind → 变量绑定,解决模糊查询兼容
速记口诀:
if 判断,choose 选,
where 去 and,set 去逗号,
trim 万能,foreach 遍历,
bind 绑变量,模糊查询好。六、面试要点
| 问题 | 答案要点 |
|---|---|
| 动态 SQL 有哪些标签? | if, choose, where, set, trim, foreach, bind |
| where 标签的作用? | 自动加 WHERE,去除多余 AND/OR |
| foreach 常用场景? | IN 查询、批量插入、批量删除 |
| #{ } 和 ${ } 在动态 SQL 中? | 排序用 ${},参数值用 #{} |
