在MyBatis框架中处理复杂查询条件时,动态SQL是我们的得力助手。今天要重点拆解的是<choose>、<when>和<otherwise>这套组合标签,它相当于Java中的if-else if-else逻辑结构,但有着独特的XML实现方式和应用场景。
这三个标签总是配合使用,形成完整的条件判断逻辑链:
xml复制<choose>
<when test="条件表达式1">
<!-- 满足条件1时执行的SQL片段 -->
</when>
<when test="条件表达式2">
<!-- 满足条件2时执行的SQL片段 -->
</when>
<otherwise>
<!-- 所有when条件都不满足时执行的SQL片段 -->
</otherwise>
</choose>
重要规则:
<when>至少需要一个,而<otherwise>最多只能有一个。这与Java语法中else if可以无限延伸但else只能有一个的设计理念一致。
很多初学者容易混淆<if>和<choose>的使用场景,它们的本质区别在于:
<if>是独立判断,每个条件都会单独评估,可能多个条件同时生效<choose>是互斥选择,一旦某个<when>条件满足,就会跳过后续判断假设我们要根据用户等级显示不同优惠信息:
xml复制<!-- if实现(可能同时满足多个条件) -->
<if test="vipLevel == 1">送10元券</if>
<if test="vipLevel >= 1">免运费</if>
<!-- choose实现(互斥执行) -->
<choose>
<when test="vipLevel == 3">8折优惠</when>
<when test="vipLevel == 2">9折优惠</when>
<otherwise>无折扣</otherwise>
</choose>
考虑电商系统中的订单查询功能,前端可能传入多种查询条件组合:
xml复制<select id="findOrders" resultType="Order">
SELECT * FROM orders
WHERE status = 'VALID'
<choose>
<when test="orderNo != null">
AND order_no = #{orderNo}
</when>
<when test="userId != null and createTime != null">
AND user_id = #{userId}
AND create_time > #{createTime}
</when>
<when test="minAmount != null">
AND amount >= #{minAmount}
</when>
<otherwise>
AND create_time > CURRENT_DATE - 7
</otherwise>
</choose>
ORDER BY create_time DESC
</select>
这个例子展示了几个关键技巧:
在update操作中,我们经常需要根据业务场景决定更新哪些字段:
xml复制<update id="updateUserSelective">
UPDATE users
<set>
<choose>
<when test="forceReset != null and forceReset">
password = #{newPassword},
salt = #{newSalt},
update_time = NOW()
</when>
<otherwise>
<if test="username != null">username=#{username},</if>
<if test="email != null">email=#{email},</if>
update_time = NOW()
</otherwise>
</choose>
</set>
WHERE id = #{id}
</update>
这种模式特别适合:
<choose>可以与其他动态SQL标签自由组合,形成更复杂的逻辑:
xml复制<select id="searchProducts" resultType="Product">
SELECT * FROM products
WHERE 1=1
<choose>
<when test="categoryIds != null and categoryIds.size() > 0">
AND category_id IN
<foreach item="cid" collection="categoryIds"
open="(" separator="," close=")">
#{cid}
</foreach>
</when>
<otherwise>
<if test="keyword != null">
AND (name LIKE CONCAT('%',#{keyword},'%')
OR description LIKE CONCAT('%',#{keyword},'%'))
</if>
</otherwise>
</choose>
<choose>
<when test="sortByPrice">
ORDER BY price
<if test="!ascending">DESC</if>
</when>
<otherwise>
ORDER BY sales_count DESC
</otherwise>
</choose>
LIMIT #{limit}
</select>
使用<choose>时要注意其对SQL执行计划的影响:
错误示例:
xml复制<choose>
<!-- 这两个条件会使用不同索引 -->
<when test="type == 1">AND code = #{value}</when>
<when test="type == 2">AND name LIKE #{value}</when>
</choose>
症状:when条件明明满足但未生效
排查步骤:
test="name != ''" vs test="name != null"典型错误:
xml复制<!-- 错误:when放在choose外面 -->
<when test="...">...</when>
<choose>...</choose>
<!-- 错误:多个otherwise -->
<choose>
<when test="...">...</when>
<otherwise>...</otherwise>
<otherwise>...</otherwise> <!-- 重复 -->
</choose>
在test表达式中使用特殊符号时需要转义:
xml复制<!-- 正确写法 -->
<when test="amount < 100">...</when>
<!-- 错误写法 -->
<when test="amount < 100">...</when>
经过多个项目的实战验证,我总结出以下经验:
<choose><otherwise>作为保底逻辑<choose>块内<when>最好不要超过5个一个典型的权限判断示例:
xml复制<choose>
<when test="@com.example.SecurityUtils@isAdmin(userId)">
SELECT * FROM sensitive_data
</when>
<when test="@com.example.SecurityUtils@isManager(userId)">
SELECT * FROM sensitive_data WHERE department = #{dept}
</when>
<otherwise>
SELECT * FROM public_data
</otherwise>
</choose>
在实际项目中,合理运用<choose>/<when>/<otherwise>三元组,可以写出既灵活又易于维护的动态SQL。特别是在处理多维度查询、分级权限控制、差异化更新等场景时,这套标签能大幅减少重复代码,让SQL保持清晰可读。