Skip to content

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 关键字,并去除内容开头多余的 ANDOR

语法:

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每次迭代的元素变量名(不可为 msgenv
indexindex当前迭代索引的变量名
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 名称不可为 msgenv(框架内部保留字)
  • 集合路径支持嵌套,如 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>