XML Dynamic SQL
参考 MyBatis 的动态 SQL 设计,支持在 XML 中编写带条件判断、循环、裁剪等逻辑的动态 SQL 语句。
参数绑定语法
框架支持两种参数语法:
| 语法 | 说明 | 示例 |
|---|---|---|
#{field} | 预编译参数占位符(防 SQL 注入,推荐) | #{msg.name} |
${field} | 直接字符串替换(用于列名、表名等) | ${msg.orderColumn} |
支持嵌套路径访问:
#{msg.user.address.city} -- 访问对象嵌套属性
#{msg.list[0]} -- 访问数组第 0 个元素
#{msg.list[0].name} -- 访问数组元素的属性⚠️
${}不做任何转义,不要将用户输入直接传入,存在 SQL 注入风险。
标签总览
| 标签 | 用途 |
|---|---|
<if> | 条件判断,满足则拼入 SQL |
<choose> / <when> / <otherwise> | 多分支选择(类似 switch/if-else) |
<where> | 自动添加 WHERE 并去除多余 AND/OR |
<set> | 自动添加 SET 并去除末尾多余逗号 |
<trim> | 自定义前缀、后缀及裁剪规则 |
<foreach> | 遍历集合,生成 IN 列表或批量插入等 |
if — 条件判断
当 test 表达式为 true 时,将标签内的 SQL 拼入结果。
语法:
xml
<if test="表达式">
SQL 片段
</if>示例:
xml
SELECT * FROM user
<where>
<if test="msg.name != null and msg.name != ''">
AND name = #{msg.name}
</if>
<if test="msg.age != null">
AND age = #{msg.age}
</if>
</where>说明:
test支持标准布尔表达式,如!=、==、&&、||、!- 多个条件可用
and/or连接(大小写均可) - 字符串非空判断建议同时判断
!= null和!= ''
choose / when / otherwise — 分支选择
类似 switch 语句,只执行第一个满足条件的 <when>,所有 <when> 均不满足时执行 <otherwise>。
语法:
xml
<choose>
<when test="表达式1">SQL 片段1</when>
<when test="表达式2">SQL 片段2</when>
<otherwise>默认 SQL 片段</otherwise>
</choose>示例:
xml
SELECT * FROM user
<where>
<choose>
<when test="msg.id != null">
AND id = #{msg.id}
</when>
<when test="msg.name != null">
AND name = #{msg.name}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>说明:
<otherwise>是可选的,可以省略- 从上到下匹配,命中第一个后不再继续
where — 智能 WHERE 子句
自动在有内容时添加 WHERE 关键字,并去除内容开头多余的 AND 或 OR。
语法:
xml
<where>
条件片段...
</where>示例:
xml
SELECT * FROM order
<where>
<if test="msg.userId != null">
AND user_id = #{msg.userId}
</if>
<if test="msg.status != null">
AND status = #{msg.status}
</if>
</where>生成结果(当 userId=1, status=null):
sql
SELECT * FROM order WHERE user_id = ?说明:
- 所有内容为空时,不输出
WHERE - 自动去除 SQL 开头的
AND/OR(不区分大小写) - 无需手动写
WHERE 1=1之类的技巧
set — 智能 SET 子句
用于 UPDATE 语句,自动添加 SET 关键字,并去除末尾多余的逗号。
语法:
xml
<set>
字段赋值片段...
</set>示例:
xml
UPDATE user
<set>
<if test="msg.name != null">
name = #{msg.name},
</if>
<if test="msg.email != null">
email = #{msg.email},
</if>
</set>
WHERE id = #{msg.id}生成结果(当 name='Tom', email=null):
sql
UPDATE user SET name = ? WHERE id = ?说明:
- 自动去除最后一个字段后面的逗号
- 所有字段均为空时,不输出
SET(此时 SQL 可能不合法,业务层需自行保障至少有一个字段)
trim — 自定义前后缀裁剪
比 <where> 和 <set> 更通用的裁剪标签,可自定义前缀、后缀及需要覆盖(删除)的内容。
语法:
xml
<trim prefix="前缀" suffix="后缀" suffixOverrides="要删除的后缀">
SQL 片段
</trim>属性说明:
| 属性 | 是否必填 | 说明 |
|---|---|---|
prefix | 否 | 有内容时,在开头添加的字符串 |
suffix | 否 | 有内容时,在末尾添加的字符串 |
suffixOverrides | 否 | 去除末尾匹配的字符串,多个用 | 分隔 |
示例 — 等价于 <where>:
xml
<trim prefix="WHERE" suffixOverrides="AND |OR ">
<if test="msg.name != null">name = #{msg.name} AND </if>
<if test="msg.age != null">age = #{msg.age} AND </if>
</trim>示例 — 等价于 <set>:
xml
<trim prefix="SET" suffixOverrides=",">
<if test="msg.name != null">name = #{msg.name},</if>
<if test="msg.email != null">email = #{msg.email},</if>
</trim>foreach — 集合遍历
遍历一个集合,常用于 IN 查询或批量插入。
语法:
xml
<foreach collection="集合路径" item="元素变量名" index="索引变量名"
open="开始符" close="结束符" separator="分隔符">
SQL 片段(可使用 #{item} 或 #{item.field})
</foreach>属性说明:
| 属性 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
collection | 是 | — | 绑定数据中集合的路径 |
item | 是 | — | 每次迭代的元素变量名(不可为 msg 或 env) |
index | 否 | index | 当前迭代索引的变量名 |
open | 否 | ( | 整体开始字符 |
close | 否 | ) | 整体结束字符 |
separator | 否 | , | 每个元素之间的分隔符 |
示例 — IN 查询:
xml
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>生成结果(ids=[1,2,3]):
sql
SELECT * FROM user WHERE id IN (?, ?, ?)示例 — 批量插入:
xml
INSERT INTO user (name, age) VALUES
<foreach collection="users" item="u" separator=",">
(#{u.name}, #{u.age})
</foreach>示例 — 使用 index:
xml
<foreach collection="items" item="item" index="i" separator=" OR ">
(sort = #{i} AND name = #{item.name})
</foreach>说明:
item名称不可为msg或env(框架内部保留字)- 集合路径支持嵌套,如
user.tags - 内部可使用
#{item.field}访问元素属性
表达式语法
test 属性和 ${} 中均使用表达式引擎求值,支持以下语法:
比较运算:
js
age > 18
name != null
name != '' and name != null
status == 1逻辑运算:
js
a != null and b != null
a == null or b == null
!(flag)访问路径:
js
user.name != null
list[0] != null完整示例
复杂查询
xml
SELECT id, name, email, status
FROM user
<where>
<if test="msg.keyword != null and msg.keyword != ''">
AND (name LIKE #{msg.keyword} OR email LIKE #{msg.keyword})
</if>
<if test="msg.status != null">
AND status = #{msg.status}
</if>
<if test="msg.ids != null">
AND id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
<choose>
<when test="msg.startTime != null and msg.endTime != null">
AND created_at BETWEEN #{msg.startTime} AND #{msg.endTime}
</when>
<when test="msg.startTime != null">
AND created_at >= #{msg.startTime}
</when>
</choose>
</where>
ORDER BY ${msg.orderColumn} ${msg.orderDir}
LIMIT #{msg.pageSize} OFFSET #{msg.offset}动态更新
xml
UPDATE user
<set>
<if test="msg.name != null">name = #{msg.name},</if>
<if test="msg.email != null">email = #{msg.email},</if>
<if test="msg.status != null">status = #{msg.status},</if>
updated_at = NOW(),
</set>
WHERE id = #{msg.id}批量插入
xml
INSERT INTO tag (user_id, name, sort)
VALUES
<foreach collection="tags" item="tag" index="i" separator=",">
(#{userId}, #{tag.name}, #{i})
</foreach>