mybatis 等价于 if-else if-else 链而非 switch,首个为 true 的 后续全跳过;必须含至少一个 , 可选但建议添加;ognl 表达式需用单引号、显式判空,且不支持赋值。

MyBatis <choose></choose> 真的不等于 Java 的 switch
它更接近 if-else if-else 链,不是 switch 的“匹配后跳出”逻辑。一旦某个 <when></when> 的 test 表达式为 true,后续 <when></when> 就彻底跳过,哪怕它们也满足条件 —— 这点和 if-else if-else 一致,但和 switch(尤其带 break 缺失时)容易混淆。
常见错误现象:
- 写了多个 <when test="status == 'A'"></when> 和 <when test="status != null"></when>,结果后者永远不生效;
- 误以为 <otherwise></otherwise> 是“兜底默认分支”,却没意识到它只在所有 <when></when> 都为 false 时才触发。
-
<choose></choose>必须包含至少一个<when></when>,<otherwise></otherwise>可选但强烈建议加上,避免 SQL 拼接为空 -
test表达式用的是 OGNL,不是 Java 语法:字符串要用单引号'A',不能写双引号"A";空值判断用obj == null,不是obj == null || obj.isEmpty()(后者需额外写) - 所有
<when></when>和<otherwise></otherwise>必须是<choose></choose>的直接子节点,嵌套在<where></where>或<set></set>里没问题,但不能被<if></if>包着
怎么写一个安全可用的 <choose></choose> 查询条件
典型场景:按不同字段组合查询用户,比如优先按 id 查,其次按 username,最后按 email,都不提供就查全部。
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="username != null and username != ''">
username = #{username}
</when>
<when test="email != null and email != ''">
email = #{email}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</where>
</select>
注意点:
- <where></where> 会自动处理前导 AND,所以每个 <when></when> 里只写字段等式,别加 AND;
- <otherwise></otherwise> 里写 1 = 1 是为了保证 <where></where> 不丢掉,否则整个 WHERE 子句可能消失;
- 字符串非空判断必须显式写 != '',因为 OGNL 中 '' 是空字符串,null 是空引用,二者不等价。
<choose></choose> 在 <set></set> 里更新字段的坑
更新语句中用 <choose></choose> 控制哪些字段要改,很容易漏掉逗号或拼错结构。
错误写法:<set><choose><when test="name != null">name = #{name}</when><when test="age != null">, age = #{age}</when></choose></set>
问题:第二个 <when></when> 自己加逗号,但第一个没加,导致开头多逗号或结尾少逗号。
- 正确做法:每个
<when></when>内部写完整赋值语句(含逗号),靠<set></set>自动去首尾逗号 - 示例:
<set> <choose> <when test="name != null">name = #{name},</when> <when test="age != null">age = #{age},</when> <when test="email != null">email = #{email},</when> </choose> </set> -
<set></set>会删掉最后一个逗号,所以每个分支末尾都加逗号是安全的;但<otherwise></otherwise>在这里意义不大,更新场景通常不需要“全字段兜底”
性能和兼容性:别在 <choose></choose> 里塞太重的表达式
OGNL 表达式在 MyBatis 解析 SQL 时执行,不是运行时由数据库执行。所有 test 判断都在 JVM 里做,所以:
立即学习“Java免费学习笔记(深入)”;
- 避免调用耗时方法,比如
test="userService.isValid(username)"—— 这会在每次 SQL 构造时执行,且无法缓存 - 不要依赖 Spring Bean 或上下文对象,除非你确认 MyBatis 配置了
configuration.setVariables(...)并传入了对应变量 - MyBatis 3.4+ 对 OGNL 做了缓存,但复杂嵌套表达式(如
list[0].name != null)仍可能触发反射,影响首次解析速度 - 如果你用的是 Kotlin 或 Lombok 生成的类,注意 getter 方法名是否符合 JavaBean 规范,否则
user.name可能取不到值
最常被忽略的一点:所有 <choose></choose> 分支共享同一个参数对象,没有作用域隔离。别指望在一个 <when></when> 里给变量赋值,然后在下一个里用 —— OGNL 不支持赋值语句,test 里只能是表达式。










