告别if-else泥潭:MyBatis动态SQL的choose/when/otherwise实战指南
在复杂的业务系统中,我们常常遇到需要根据不同条件动态选择数据表或查询字段的场景。传统做法是堆砌大量if-else语句,这不仅使代码臃肿难维护,还容易引发SQL拼接错误。MyBatis提供的choose/when/otherwise标签组合,就像Java中的switch-case结构,能够优雅地解决这类问题。
1. 为什么需要choose/when/otherwise?
想象一个电商平台,订单数据按照不同业务线分表存储:orders_retail、orders_wholesale和orders_international。当我们需要根据用户选择的业务线查询订单时,传统if标签方案会是这样的:
<select id="findOrders" resultType="Order"> SELECT * FROM <if test="businessLine == 'retail'"> orders_retail </if> <if test="businessLine == 'wholesale'"> orders_wholesale </if> <if test="businessLine == 'international'"> orders_international </if> WHERE status = #{status} </select>这种写法存在几个明显问题:
- 可读性差:随着条件增多,代码会变得冗长难懂
- 维护困难:新增业务线时需要添加新的if块
- 潜在风险:所有if条件都不满足时,SQL语句将缺少表名导致语法错误
2. choose/when/otherwise基础用法
choose/when/otherwise组合提供了更结构化的条件判断方式。上面的例子可以改写为:
<select id="findOrders" resultType="Order"> SELECT * FROM <choose> <when test="businessLine == 'retail'"> orders_retail </when> <when test="businessLine == 'wholesale'"> orders_wholesale </when> <when test="businessLine == 'international'"> orders_international </when> <otherwise> orders_default </otherwise> </choose> WHERE status = #{status} </select>关键特点:
- choose:作为容器,类似Java中的switch
- when:定义具体条件分支,类似case
- otherwise:默认分支,类似default
提示:otherwise不是必须的,但建议总是包含它以避免SQL语法错误
3. 高级应用场景
3.1 多租户数据隔离
在SaaS系统中,不同租户的数据可能存储在不同表中。使用choose可以轻松实现租户数据的动态路由:
<select id="findUserByTenant" resultType="User"> SELECT * FROM <choose> <when test="tenantId == 1"> tenant_1_users </when> <when test="tenantId == 2"> tenant_2_users </when> <otherwise> common_users </otherwise> </choose> WHERE username = #{username} </select>3.2 动态字段选择
除了表名,我们还可以动态选择查询字段:
<select id="findProduct" resultType="Product"> SELECT id, name, price, <choose> <when test="userLevel == 'VIP'"> cost_price, profit_margin </when> <otherwise> discount_rate </otherwise> </choose> FROM products WHERE id = #{id} </select>3.3 复杂条件组合
when标签支持复杂的OGNL表达式,可以实现多条件判断:
<select id="findOrders" resultType="Order"> SELECT * FROM orders <where> <choose> <when test="status != null and createTime != null"> status = #{status} AND create_time > #{createTime} </when> <when test="status != null"> status = #{status} </when> <when test="createTime != null"> create_time > #{createTime} </when> <otherwise> status = 'ACTIVE' </otherwise> </choose> </where> </select>4. 性能优化与最佳实践
虽然choose/when/otherwise提供了强大的灵活性,但也需要注意一些性能和使用技巧:
4.1 与where标签配合使用
where标签可以智能处理条件前的AND/OR关键字,与choose组合使用效果更佳:
<select id="searchOrders" resultType="Order"> SELECT * FROM orders <where> <choose> <when test="type == 'VIP'"> AND vip_flag = 1 <if test="region != null"> AND region = #{region} </if> </when> <otherwise> AND status = 'ACTIVE' </otherwise> </choose> </where> </select>4.2 避免过度嵌套
虽然choose支持嵌套,但过度嵌套会降低可读性:
<!-- 不推荐 --> <choose> <when test="condition1"> <choose> <when test="subCondition1"> ... </when> </choose> </when> </choose>4.3 性能对比
不同条件判断方式的性能特点:
| 方式 | 可读性 | 维护性 | 性能 | 适用场景 |
|---|---|---|---|---|
| if标签 | 一般 | 差 | 高 | 简单条件 |
| choose/when | 优 | 优 | 中 | 互斥条件 |
| 数据库视图 | 优 | 优 | 低 | 复杂固定逻辑 |
5. 常见问题解决方案
5.1 处理空字符串
当参数可能为空字符串时,应该先进行trim处理:
<when test="platformType != null and platformType.trim() != ''"> ... </when>5.2 枚举值比较
与枚举值比较时,可以直接使用枚举名称:
<when test="orderType == 'ONLINE'"> orders_online </when>5.3 多条件优先级
when标签会按顺序判断,第一个满足条件的when会被执行:
<choose> <when test="user.role == 'ADMIN'"> <!-- 管理员优先 --> </when> <when test="user.vipLevel > 3"> <!-- 高级VIP --> </when> </choose>在实际项目中,我发现合理使用choose/when/otherwise可以显著减少XML中的条件判断复杂度。特别是在处理分表查询时,它能清晰地表达业务逻辑,让SQL映射文件更易于维护。一个实用的技巧是为otherwise分支添加日志记录,这样当出现意外情况时,我们可以快速发现问题所在。