1. 动态SQL中的条件分支控制
在MyBatis等ORM框架中,动态SQL的choose/when/otherwise结构相当于编程语言中的switch-case-default语句。这种结构特别适合处理多条件分支的场景,相比一连串的if语句更加清晰易读。
1.1 choose-when-otherwise的基本语法结构
xml复制<choose>
<when test="条件表达式1">
<!-- 满足条件1时执行的SQL片段 -->
</when>
<when test="条件表达式2">
<!-- 满足条件2时执行的SQL片段 -->
</when>
<otherwise>
<!-- 所有when条件都不满足时执行的默认SQL片段 -->
</otherwise>
</choose>
这个结构的工作方式是顺序评估每个when子句的test属性,一旦某个条件为真,就使用对应的SQL片段,后续的when子句将不再评估。如果所有when条件都不满足,则执行otherwise中的内容(如果有的话)。
1.2 与if语句的关键区别
虽然if标签也能实现类似功能,但choose/when/otherwise有几个独特优势:
- 互斥性:一旦某个
when条件满足,就会跳过其余条件的评估,这在某些场景下能提升性能 - 默认处理:
otherwise提供了明确的默认处理路径,代码意图更清晰 - 结构清晰:对于多条件分支,
choose结构比嵌套的if更易于阅读和维护
提示:当条件之间存在互斥关系(即一个条件成立时其他条件不可能成立)时,优先使用
choose而不是多个独立的if语句。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实际应用场景与案例解析
2.1 用户查询过滤器的实现
假设我们需要实现一个用户查询功能,支持按不同条件筛选用户:
xml复制<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE status = 'ACTIVE'
<choose>
<when test="role != null">
AND role = #{role}
</when>
<when test="departmentId != null">
AND department_id = #{departmentId}
</when>
<otherwise>
AND last_login_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
</otherwise>
</choose>
</select>
这个例子中:
- 如果提供了
role参数,则按角色过滤 - 如果没有
role但有departmentId,则按部门过滤 - 如果两者都没有,则默认只查询最近30天登录过的活跃用户
2.2 多级权限检查
在权限系统中,choose结构可以优雅地处理多级权限检查:
xml复制<select id="getAccessibleResources" resultType="Resource">
SELECT * FROM resources
<choose>
<when test="user.isAdmin">
WHERE 1=1 <!-- 管理员可以访问所有资源 -->
</when>
<when test="user.role == 'MANAGER'">
WHERE department_id = #{user.departmentId}
</when>
<otherwise>
WHERE creator_id = #{user.id}
</otherwise>
</choose>
</select>
2.3 动态排序实现
实现动态排序时,choose结构可以避免SQL注入风险:
xml复制<select id="findProducts" resultType="Product">
SELECT * FROM products
ORDER BY
<choose>
<when test="sortBy == 'price'">
price
</when>
<when test="sortBy == 'sales'">
sales_volume
</when>
<otherwise>
create_time
</otherwise>
</choose>
<if test="order != null">
${order} <!-- 注意:这里使用${}是安全的,因为值已经被限制 -->
</if>
</select>
3. 高级用法与性能优化
3.1 嵌套choose结构
choose可以嵌套使用来处理更复杂的条件逻辑:
xml复制<select id="searchDocuments" resultType="Document">
SELECT * FROM documents
<choose>
<when test="user.privilegeLevel > 1">
WHERE 1=1 <!-- 高级用户无限制 -->
<if test="keywords != null">
AND content LIKE CONCAT('%', #{keywords}, '%')
</if>
</when>
<otherwise>
<choose>
<when test="user.department == 'HR'">
WHERE visibility IN ('PUBLIC', 'HR_INTERNAL')
</when>
<otherwise>
WHERE visibility = 'PUBLIC'
</otherwise>
</choose>
<if test="keywords != null">
AND content LIKE CONCAT('%', #{keywords}, '%')
</if>
</otherwise>
</choose>
</select>
3.2 与其他动态SQL标签的组合
choose可以与if、foreach等标签组合使用:
xml复制<update id="updateUserProfile">
UPDATE users
<set>
<if test="username != null">username = #{username},</if>
<choose>
<when test="avatar != null">
avatar = #{avatar},
</when>
<when test="defaultAvatar != null">
avatar = #{defaultAvatar},
</when>
<otherwise>
avatar = DEFAULT,
</otherwise>
</choose>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
3.3 性能优化建议
- 条件顺序:将最可能满足的条件放在前面,减少评估次数
- 避免复杂表达式:
test属性中的表达式应尽量简单,复杂逻辑建议在Java代码中预处理 - 合理使用otherwise:明确的默认行为比隐式的默认行为(什么都不做)更好
- 索引考虑:确保
choose中选择的列都有适当的索引
4. 常见问题与调试技巧
4.1 常见错误模式
- 缺少otherwise:当所有
when条件都不满足且没有otherwise时,可能导致意外的SQL片段
xml复制<!-- 危险:如果status既不是1也不是2,WHERE子句可能变成WHERE AND ... -->
<select id="findItems">
SELECT * FROM items
WHERE
<choose>
<when test="status == 1">
status = 1
</when>
<when test="status == 2">
status = 2
</when>
</choose>
AND category = #{category}
</select>
修正方案:
xml复制<select id="findItems">
SELECT * FROM items
WHERE
<choose>
<when test="status == 1">
status = 1
</when>
<when test="status == 2">
status = 2
</when>
<otherwise>
1=1
</otherwise>
</choose>
AND category = #{category}
</select>
- test表达式错误:OGNL表达式中的常见错误
xml复制<!-- 错误:字符串比较应使用双引号 -->
<when test="type == 'VIP'">
<!-- 正确 -->
<when test='type == "VIP"'>
4.2 调试技巧
- 日志输出:开启MyBatis的DEBUG日志,查看最终生成的SQL
- 条件评估测试:单独测试OGNL表达式是否返回预期结果
- 简化测试:逐步移除
when条件,定位问题所在 - XML格式化:保持XML的良好缩进,提高可读性
4.3 与CASE WHEN的对比
SQL中的CASE WHEN与MyBatis的choose/when有相似之处,但作用在不同层面:
| 特性 | MyBatis choose/when | SQL CASE WHEN |
|---|---|---|
| 执行阶段 | SQL生成阶段 | 数据库执行阶段 |
| 用途 | 动态构建SQL语句 | 数据行的条件判断与转换 |
| 性能影响 | 影响SQL生成效率 | 影响查询执行效率 |
| 可读性 | 在XML中更清晰 | 在SQL中更直接 |
| 条件复杂度 | 支持复杂Java对象属性访问 | 通常基于列值简单比较 |
示例:两者可以结合使用
xml复制<select id="getUserStatusReport">
SELECT
username,
<choose>
<when test="useSimpleReport">
CASE WHEN status = 1 THEN 'Active' ELSE 'Inactive' END AS status_text
</when>
<otherwise>
CASE
WHEN status = 1 THEN 'Active'
WHEN status = 2 THEN 'Suspended'
WHEN status = 3 THEN 'Terminated'
ELSE 'Unknown'
END AS status_text
</otherwise>
</choose>
FROM users
</select>
