← 返回题目列表

MyBatis 动态 SQL 常用标签有哪些?原理是什么?

高频 中等 第 5 / 24 题 更新于 2026/07/26
MyBatis动态 SQLOGNLforeach

简化版

MyBatis 动态 SQL 用 ifchoose/when/otherwisewheresettrimforeach 等节点,根据参数在运行时生成 SQL。判断表达式由 OGNL 计算,最终生成 BoundSql;值仍应使用 #{} 参数化,动态 SQL 不等于可以安全使用 ${} 拼接用户输入。

详细版

where 只在内部有内容时输出 WHERE,并去掉开头多余的 AND/OR;set 为更新语句输出 SET 并移除末尾逗号;trim 可自定义前后缀与需要删除的文本。choose 类似互斥分支,foreach 适合 IN 条件和批量 values,bind 可创建供后续使用的 OGNL 变量。

动态 SQL 在 SqlSource 计算阶段生成 SQL 文本和 ParameterMapping。过多业务规则放在 XML 会难以测试,空集合、全空更新条件和无 WHERE 删除必须显式防护。

完整版教学

一、if 与 where

SELECT id, name, status FROM users
<where>
  <if test="name != null and name != ''">
    AND name = #{name}
  </if>
  <if test="status != null">
    AND status = #{status}
  </if>
</where>

没有条件时 where 不输出,有条件时自动处理前导 AND/OR,避免手工判断第一个条件。它只修整 SQL 片段,不验证查询范围是否符合业务安全要求。

标签解决的问题典型场景
if条件满足才拼接片段可选查询条件、可选更新字段
choose/when/otherwise多个分支只选一个id、email、手机号三选一查询
where自动加 WHERE 并移除开头 AND/OR动态查询条件
set自动加 SET 并移除末尾逗号动态更新字段
foreach遍历集合生成多个占位符IN 查询、批量 values
trim自定义前缀后缀和修剪规则自定义 where/set 形态

记忆钩子:动态 SQL 不是“字符串随便拼”,而是 MyBatis 先用节点树生成 SQL 结构,再让 #{} 负责参数绑定。

二、choose 表达互斥分支

<choose>
  <when test="id != null">WHERE id = #{id}</when>
  <when test="email != null">WHERE email = #{email}</when>
  <otherwise>WHERE status = 'ACTIVE'</otherwise>
</choose>

choose 只选择第一个满足的 when,否则走 otherwise;多个独立 if 则可能同时拼接。选择哪个取决于条件能否共存。

三、set 防止多余逗号

UPDATE users
<set>
  <if test="name != null">name = #{name},</if>
  <if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}

如果所有更新字段都为空,set 可能没有有效内容,最终 SQL 仍会非法。服务层或 SQL 中应明确禁止“没有任何修改字段”的请求。

四、foreach 展开集合

foreach 可指定 collection、item、index、open、close、separator,为每个元素创建独立上下文和参数映射。除 IN 外,也可生成批量 INSERT 的多组 values。

空集合必须先定义语义:返回空结果、跳过语句还是报参数错误。绝不能因 IN 条件消失而意外执行全表查询或更新。

WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
  #{id}
</foreach>

如果 ids = [10, 20, 30],最终 SQL 形态接近 id IN (?, ?, ?),并生成 3 个 ParameterMapping。若 ids 有 2000 个元素,SQL 里就有 2000 个占位符,可能碰到数据库参数个数或 SQL 长度限制,所以大集合要分片或改临时表方案。

五、trim、bind 与 sql/include

trim 是 where 和 set 背后的通用修整能力,可配置 prefix、suffix、prefixOverrides、suffixOverrides。bind 能用 OGNL 预先计算变量,例如构造 LIKE 参数,再通过 #{pattern} 安全绑定。

<sql><include> 可复用静态或少量动态片段,但复用层级过深会让最终 SQL 难以阅读。排错时应开启合适 SQL 日志或测试 BoundSql,而不是只看碎片文件。

六、动态 SQL 的安全边界

OGNL 条件决定拼不拼某段 SQL,#{} 决定值如何参数化,二者职责不同。表名、排序列等确需文本结构时必须从固定白名单选择,不能把请求参数直接交给 ${}

删除和更新尤其要设置强制主键/租户条件,并通过测试覆盖所有条件为空的分支。

七、常见误区与追问

  • 误区:用了动态 SQL 就可以放心使用 ${} 动态节点只决定片段是否出现,用户值仍应使用 #{} 参数化,${} 只能用于白名单结构片段。
  • 误区:where 会保证查询一定安全。 where 只修剪语法,不会自动防止条件全空导致全表查询。
  • 追问:choose 和多个 if 有什么区别? choose 只命中第一个满足分支,多个 if 可以同时拼接,适合不同业务语义。
  • 追问:set 还需要服务层校验吗? 需要,所有字段为空时 SQL 可能非法,或者业务上不应允许空更新。
  • 误区:foreach 空集合自然等于查不到。 空集合可能生成非法 SQL,也可能导致条件消失,必须显式定义空集合语义。
  • 追问:如何排查动态 SQL 最终生成了什么? 开启 SQL 日志、在测试中检查 BoundSql,关注 SQL 文本、ParameterMapping 和附加参数。

八、加强记忆

if 决定可选条件,choose 选一个分支,where/set/trim 修整语法,foreach 展开集合。动态节点生成 BoundSql,但值仍走 #{};最危险的边界是空集合、空更新字段和 WHERE 条件全部消失。