mybatis动态sql中不支持跨标签变量共享,是顺序匹配的if-else结构,需配合判空,xml无法处理复杂逻辑,应下沉至java层。

MyBatis 中 <if></if> 标签的嵌套与作用域限制
MyBatis 的 <if></if> 不支持跨标签共享变量,每个 <if></if> 内部的表达式独立求值,且不能直接引用其他 <if></if> 中定义的临时变量。常见错误是试图在多个 <if></if> 间传递中间结果,比如先判断 status != null,再在后续 <if></if> 中用 status == 'ACTIVE' —— 这本身合法,但若中间加了 <set></set> 或拼接逻辑,就容易因 SQL 片段缺失导致语法错误。
实操建议:
-
test属性必须是 OGNL 表达式,status == 'ACTIVE'要写成status == 'ACTIVE'(单引号),不能用双引号(会被 XML 解析器提前截断) - 多个条件并列时,优先用
<where></where>+ 多个<if></if>,它会自动处理开头的AND/WHERE冗余问题 - 避免在
<if></if>内写复杂 Java 逻辑;需要分支计算时,应前置到 DAO 方法或 Service 层完成
MyBatis <choose></choose> 与 Java switch 的语义差异
<choose></choose> 更接近 if-else if-else,不是严格意义上的 switch:它按顺序匹配第一个为 true 的 <when></when>,其余全部跳过;没有“case 落空后执行 default”的隐式兜底机制,<otherwise></otherwise> 必须显式写出,且只能有一个。
常见误用:
- 把
<when test="type == 1"></when>和<when test="type == '1'"></when>并列写,因类型不一致导致两个都不命中 - 遗漏
<otherwise></otherwise>,而业务上又存在未覆盖的输入值,最终生成空 SQL 片段,可能引发全表扫描 -
<choose></choose>套在<update></update>内时,若所有<when></when>都不满足,整个<set></set>可能为空,导致 SQL 语法错误
动态 SQL 中 <foreach></foreach> 与条件组合的边界情况
<foreach></foreach> 本身不带条件判断能力,常需和 <if></if> 配合使用。典型场景是「仅当集合非空时才拼入 IN 子句」,但容易忽略空集合、null 集合、元素含 null 值这三种状态。
正确写法示例:
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</if>
注意点:
-
collection参数名必须与传入 Map 或对象属性名完全一致,区分大小写 - 若传入的是数组(如
int[]),MyBatis 默认识别为array,此时collection="array"才能遍历 -
open/close是纯字符串包裹,不参与逻辑判断;若ids为空,整个<if></if>块被跳过,不会留下孤立的AND
XML 映射中无法实现的逻辑必须移出 SQL 层
XML 映射本质是模板引擎,不支持循环内嵌套条件、递归、闭包、异常捕获等编程语言特性。例如「对列表中每个用户,如果余额 > 100 则打标,否则查其上级再判断」这类逻辑,在 XML 里强行用 <foreach></foreach> + <if></if> 堆砌会导致可读性崩溃,且无法调试。
真正可行的做法:
- 把多层条件判断逻辑下沉到 Service 层,用 Java 完整控制流程,只将最终确定的参数(如
finalStatus、targetIds)传给 Mapper - 复杂查询优先考虑视图或数据库函数封装,而不是在 MyBatis XML 里模拟业务规则
- 需要运行时决定表名或字段名(如分表场景),用
${tableName}拼接,但必须严格校验输入,杜绝 SQL 注入
最常被忽略的一点:XML 里的任意逻辑都发生在 SQL 拼接阶段,而非执行阶段——这意味着所有 test 表达式在 JDBC PreparedStatement 构造前就已求值完毕,无法响应数据库返回的动态结果做二次判断。










