MyBatis 中动态 SQL 的作用
一句话总结
MyBatis 动态 SQL 用一组 XML 标签(<if>、<choose>、<foreach>、<where>、<set> 等)在 Mapper 层按条件动态拼接 SQL,避免在 Java 代码里手动拼接字符串,实现多条件查询、批量操作和按需更新等复杂场景,提升可读性与可维护性,并配合 #{} 保证安全。
一、动态 SQL 的核心作用
1. 灵活的条件查询(多条件拼接 WHERE)
根据参数是否为空,按需拼接 WHERE 条件,避免大量 if 拼接 SQL 字符串,以及多余的 AND / OR。
xml
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
name、age都为空时:只生成SELECT * FROM user- 只有
name不为空:生成SELECT * FROM user WHERE name = ?
面试点 :动态 SQL 最典型用途之一就是多条件查询 ,根据参数是否为空动态增加
WHERE条件。
2. 减少重复代码(替代多层 if-else 分支)
使用 <choose> / <when> / <otherwise> 做互斥分支选择,避免同一个 SQL 写多份、或者在 Java 里写一堆 if-else。
xml
<select id="findUserByType" resultType="User">
SELECT * FROM user
WHERE role =
<choose>
<when test="role == 'admin'">
'ADMIN'
</when>
<when test="role == 'user'">
'USER'
</when>
<otherwise>
'GUEST'
</otherwise>
</choose>
</select>
- 优势 :
- XML 中逻辑集中统一、互斥关系清晰;
- 减少重复 SQL 片段,维护方便。
3. 动态批量操作(IN 条件、批量插入 / 更新)
通过 <foreach> 遍历集合,动态生成 IN (...) 或批量插入的 VALUES (...) 列表。
xml
<select id="selectByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids"
item="id"
open="("
separator=","
close=")">
#{id}
</foreach>
</select>
- 典型场景:批量查询、批量更新、批量删除等,减少数据库往返次数、提升性能。
4. 配合参数绑定,降低 SQL 注入风险
通过 #{} 占位符和 <bind> 动态生成参数值,防止将用户输入直接拼到 SQL 里。
xml
<select id="findByNameLike" resultType="User">
<bind name="pattern" value="'%' + name + '%'" />
SELECT * FROM user
WHERE name LIKE #{pattern}
</select>
- 即使参数中包含
' OR 1=1 --,也会被当成普通字符串,而不是 SQL 片段执行。 - 注意 :真正防注入的关键在于
#{}+ 预编译,动态 SQL 只是避免你再去拼字符串。
5. 动态更新与字段控制(按需更新非空字段)
使用 <set> 标签只更新非空字段,并自动处理多余逗号。
xml
<update id="updateUser">
UPDATE user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>
- 只会更新有值的字段,避免把字段更新成
NULL; <set>会自动去掉最后一个逗号,生成合法 SQL。
二、典型应用场景(面试可以照着说)
| 场景 | 动态 SQL 标签组合 | 效果 |
|---|---|---|
| 多条件查询 | <if> + <where> |
按需生成 WHERE 子句,避免冗余条件 |
| 分页 + 条件查询 | <if> + <where> + 分页插件 |
条件 + 分页统一在一个 Mapper 中维护 |
| 批量插入 / 更新 | <foreach> |
单条 SQL 处理多条数据,减少数据库交互 |
| 多表关联查询 | <choose> 动态选表 / 关联 |
根据业务条件选择不同的关联方式或表 |
| 动态排序 | <if> 控制 ORDER BY |
支持前端传入排序字段、排序方式,实现灵活排序 |
一句话记忆 :动态 SQL 常用在多条件查询、批量操作、动态排序、部分字段更新、复杂关联这几类场景。
三、动态 SQL 的执行原理(加分项)
-
OGNL 表达式解析
- MyBatis 使用 OGNL(Object-Graph Navigation Language)解析
<if test="...">、<foreach>等标签中的条件表达式; - 通过
test="name != null and name != ''"这类表达式判断是否拼接对应的 SQL 片段。
- MyBatis 使用 OGNL(Object-Graph Navigation Language)解析
-
SqlNode组合成SqlSource- 所有动态标签先被解析成一棵
SqlNode树(比如IfSqlNode、WhereSqlNode等); - 运行时根据入参在这棵树上"走一遍",把满足条件的节点拼接成完整 SQL,生成
SqlSource。
- 所有动态标签先被解析成一棵
-
预编译与参数绑定
- 最终 SQL 仍然通过
PreparedStatement预编译; #{}会变成?占位符,参数通过 JDBC 安全绑定,既防注入又可复用执行计划。
- 最终 SQL 仍然通过
四、实际场景示例:多条件搜索用户
xml
<select id="searchUsers" 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="roles != null and roles.size() > 0">
AND role IN
<foreach collection="roles"
item="role"
open="("
close=")"
separator=",">
#{role}
</foreach>
</if>
</where>
ORDER BY create_time DESC
</select>
- 支持:模糊查询、年龄下限过滤、角色集合过滤;
- 传哪个条件就拼哪个条件,未传的不生效;
- 所有入参都走
#{},安全、可预编译。
五、面试回答模板(直接背)
- 问:MyBatis 动态 SQL 有什么作用?常用在哪些场景?
- 答:"MyBatis 通过
<if>、<choose>、<foreach>、<where>、<set>等标签,在 XML 里按条件动态拼接 SQL,代替 Java 代码里手动拼字符串。常用在多条件查询、批量插入 / 更新、动态排序、按需更新部分字段、复杂关联查询等场景,既提高了可读性和维护性,又能配合#{}和预编译保证安全。"
- 答:"MyBatis 通过