1. 动态SQL中的分支逻辑实现
在MyBatis框架中处理复杂查询条件时,我们经常遇到需要根据不同条件执行不同SQL片段的需求。这就好比在编程语言中使用if-else语句进行流程控制,而choose-when-otherwise正是MyBatis提供的条件分支解决方案。
我曾在电商平台开发中遇到这样的场景:商品搜索需要根据用户选择的筛选条件动态生成查询语句。当用户选择价格区间时按价格过滤,未选择价格时按销量排序,两者都未选择时展示推荐商品。这种多条件分支的场景正是choose-when-otherwise的典型应用。
2. choose-when-otherwise语法解析
2.1 基础语法结构
xml复制<choose>
<when test="条件表达式1">
SQL片段1
</when>
<when test="条件表达式2">
SQL片段2
</when>
<otherwise>
默认SQL片段
</otherwise>
</choose>
这种结构与Java中的switch-case-default语句非常相似。当第一个when条件满足时,执行对应的SQL片段并跳出choose块;所有when都不满足时,执行otherwise中的内容。
2.2 条件表达式编写规范
test属性中使用的OGNL表达式需要注意:
- 参数直接使用属性名引用,如
price != null - 字符串比较要使用双引号嵌套,如
status == 'A' - 可以调用参数对象的方法,如
user.isVip() - 支持简单的逻辑运算:
&&,||,!
警告:避免在test表达式中编写复杂业务逻辑,这会导致XML难以维护。复杂判断应在前置处理中完成,仅将结果作为参数传入。
3. 实战应用场景剖析
3.1 多条件商品搜索实现
xml复制<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<choose>
<when test="categoryId != null">
AND category_id = #{categoryId}
</when>
<when test="keyword != null and keyword != ''">
AND name LIKE CONCAT('%',#{keyword},'%')
</when>
<otherwise>
AND is_featured = 1
</otherwise>
</choose>
</where>
ORDER BY
<choose>
<when test="sortByPrice">
price ${orderType}
</when>
<otherwise>
sales_volume DESC
</otherwise>
</choose>
</select>
这个例子展示了两个choose块的嵌套使用:
- 主查询条件选择:按分类ID→按关键词→默认推荐商品
- 排序策略选择:按价格排序→默认按销量降序
3.2 权限分级查询方案
在管理系统中,不同权限用户需要看到不同的数据范围:
xml复制<select id="listOrders" resultType="Order">
SELECT * FROM orders
<where>
<choose>
<when test="user.role == 'ADMIN'">
<!-- 管理员看到所有订单 -->
</when>
<when test="user.role == 'MANAGER'">
AND department_id = #{user.departmentId}
</when>
<otherwise>
AND user_id = #{user.id}
</otherwise>
</choose>
</where>
</select>
4. 高级技巧与性能优化
4.1 与其他动态标签的组合
choose可以与其他动态SQL标签灵活组合:
xml复制<select id="dynamicQuery" resultType="Result">
SELECT * FROM table
<where>
<if test="commonCondition != null">
AND col = #{commonCondition}
</if>
<choose>
<when test="type == 'A'">
<include refid="typeACondition"/>
</when>
<when test="type == 'B'">
AND status IN
<foreach item="item" collection="statusList" open="(" separator="," close=")">
#{item}
</foreach>
</when>
</choose>
</where>
</select>
4.2 性能优化建议
- 条件顺序优化:将最可能命中的when条件放在前面,减少判断次数
- 避免过度嵌套:choose嵌套层级不要超过3层,否则考虑拆分SQL
- 索引友好设计:确保每个when分支中的条件都能利用索引
- 默认情况处理:otherwise中也要考虑性能,避免全表扫描
5. 常见问题排查指南
5.1 条件不生效问题
症状:某个when分支的条件明明满足,但对应的SQL未执行
排查步骤:
- 检查test表达式语法是否正确
- 打印传入参数确认实际值
- 检查参数是否为null导致空指针
- 确认没有其他when分支先匹配
5.2 特殊字符处理
当条件中包含特殊字符时需要特别注意:
xml复制<!-- 错误示例 -->
<when test="type == '<special>'">
<!-- 正确写法 -->
<when test='type == "<special>"'>
5.3 与if标签的选择
何时使用choose而非多个if标签:
- 条件互斥时用choose(只执行一个分支)
- 条件可叠加时用if(可能执行多个分支)
- 需要默认分支时用choose
6. 实际开发中的经验总结
- 可读性维护:每个when块保持简短,复杂逻辑用
<include>引用外部片段 - 测试覆盖:确保单元测试覆盖所有when和otherwise分支
- 日志输出:在拦截器中打印最终SQL,验证条件判断结果
- 参数预处理:在Java代码中先处理复杂判断,简化XML中的条件
我在金融系统开发中曾遇到一个典型案例:费率计算规则有10余种分支情况。最初全部写在choose-when中导致XML臃肿不堪。后来调整为:
- 在Service层预处理判断逻辑
- 仅将最终决策的类型标识传入Mapper
- XML中保持简洁的choose结构
这种分层处理方式既保持了SQL的可读性,又将复杂逻辑放在更易维护的Java代码中。