choose、when、otherwise
导学
本节你将掌握 MyBatis 中用于多分支选择的动态 SQL 元素组合 choose/when/otherwise。学习目标是:
- 理解
choose与 Java 中switch-case-default的对应关系 - 掌握
when和otherwise的配合使用方式 - 能够从多个互斥条件中只选择其一执行
- 能够编写优先策略查询(如优先按 A 查,没有则按 B 查,再没有则兜底)
定义
choose/when/otherwise 是 MyBatis 动态 SQL 中用于实现多分支互斥选择的元素组合,其行为类似于 Java 中的 switch-case-default 结构。与 if 的"满足条件就拼接"不同,choose 的语义是"从多个条件中只选择第一个满足的分支执行,其余全部忽略"。
在 JDBC 编程中,实现这种"多选一"的逻辑通常需要写冗长的 if-else if-else 链:
String sql = "SELECT * FROM student WHERE ";
if (name != null && !name.isEmpty()) {
sql += "name LIKE '%" + name + "%'";
} else if (major != null && !major.isEmpty()) {
sql += "major = '" + major + "'";
} else {
sql += "score >= 90"; // 默认查高分学生
}
这种写法不仅代码冗长,而且每个分支都存在 SQL 注入风险。MyBatis 的 choose 组合将多分支选择逻辑声明式地表达在 XML 中,配合 #{ } 预编译占位符,既清晰又安全。
适用位置与核心属性
choose 作为父容器,when 和 otherwise 作为子元素嵌套在 choose 内部。
| 元素 | 属性 | 是否必填 | 说明 |
|---|---|---|---|
choose | 无 | — | 父容器,包裹多个 when 和一个可选的 otherwise |
when | test | 是 | OGNL 表达式,与 if 的 test 语义相同。可以有多个 when,按顺序匹配,第一个满足条件的 when 被选中,其余忽略 |
otherwise | 无 | — | 默认分支,当所有 when 都不满足时执行。最多一个,且必须放在所有 when 之后 |
核心原理
多分支选择流程图
与 if 的本质区别
完整示例
场景说明
乐途学院的学生管理系统需要实现一个优先策略查询:管理员查询学生时,系统按照以下优先级策略返回结果:
- 如果传入了姓名(非空),则按姓名模糊查询
- 如果没有传入姓名但传入了专业,则按专业精确查询
- 如果姓名和专业都没有传入,则默认查询所有分数大于等于 90 分的高分学生
这个场景非常适合 choose/when/otherwise,因为三个条件是互斥的,只需要执行其中一个。
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前表数据:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整的映射文件片段
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flying.mapper.StudentMapper">
<!-- 优先策略查询:姓名 > 专业 > 高分兜底 -->
<select id="findByPriority" resultType="Student"
parameterType="com.flying.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE
<choose>
<when test="name != null and name != ''">
name LIKE CONCAT('%', #{name}, '%')
</when>
<when test="major != null and major != ''">
major = #{major}
</when>
<otherwise>
score >= 90
</otherwise>
</choose>
</select>
</mapper>
StudentMapper.java(接口)
package com.flying.mapper;
import com.flying.entity.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findByPriority(Student condition);
}
实际执行结果
情况一:传入 name(第一个 when 满足)
测试代码:
Student condition = new Student();
condition.setName("小");
List<Student> list = mapper.findByPriority(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%')
参数值: 小
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
分析: 第一个 when 的 test 求值为 true,所以选中了 name LIKE 分支。虽然此时 major 为 null,但第二个 when 和 otherwise 都被跳过了。
情况二:name 为空,传入 major(第二个 when 满足)
测试代码:
Student condition = new Student();
condition.setName(""); // 空字符串,第一个 when 不满足
condition.setMajor("计算机科学");
List<Student> list = mapper.findByPriority(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE major = ?
参数值: 计算机科学
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
分析: 第一个 when 因为 name != '' 不满足而求值为 false,继续判断第二个 when,major != null and major != '' 为 true,选中该分支。otherwise 被跳过。
情况三:name 和 major 都为空(进入 otherwise)
测试代码:
Student condition = new Student();
// name 和 major 都为 null
List<Student> list = mapper.findByPriority(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE score >= 90
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
分析: 两个 when 都不满足,最终进入 otherwise 分支,执行默认的 score >= 90 查询。这确保了即使什么条件都没传,也能返回有意义的结果(高分学生列表)。
情况四:name 和 major 都传入(只选第一个满足的)
测试代码:
Student condition = new Student();
condition.setName("大");
condition.setMajor("软件工程");
List<Student> list = mapper.findByPriority(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%')
参数值: 大
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
分析: 尽管 major 也传入了,但 choose 的语义是"只选第一个满足的",所以只有第一个 when 生效。这是 choose 与多个独立 if 的核心区别。
易错场景 / 常见误区
| 误区 | 错误示例 | 后果 | 正解 |
|---|---|---|---|
把 choose 当 if 用,期望多个条件同时满足 | 在 choose 中放多个可能同时满足的 when | 只有第一个满足的生效,其余被忽略,导致查询结果不符合预期 | 如果需要多条件同时生效,应该使用多个独立的 if 配合 where |
otherwise 放在 when 前面 | <otherwise> 在 <when> 之前 | XML 解析错误,otherwise 必须放在所有 when 之后 | 调整顺序:所有 when 在前,otherwise 在最后 |
忘记写 otherwise 且所有 when 都不满足 | 没有 otherwise | choose 不输出任何内容,SQL 变成 WHERE 后面没有条件,语法错误 | 添加 otherwise 作为兜底,或在 choose 外配合 where 元素使用 |
在 when 的 test 中使用 && | <when test="a!=null && b!=null"> | 部分 OGNL 版本不支持 &&,解析失败 | 使用 and 代替:a != null and b != null |
面试考点
Q1:choose/when/otherwise 与多个 if 有什么区别?
A:
choose是互斥选择,从多个分支中只选第一个满足条件的分支执行,其余全部忽略;而多个独立的if是并列关系,每个if只要条件满足就会拼接对应的 SQL 片段,多个条件可以同时生效。choose类似于 Java 的if-else if-else,if类似于多个独立的if。
Q2:以下场景应该选 if 还是 choose?
场景 A:用户可以同时按姓名和专业查询,也可以只按其中一个查询。 场景 B:系统优先按姓名查,没有姓名则按专业查,再没有则查全部高分学生。
A:场景 A 应该使用多个
if配合where,因为两个条件可以同时生效;场景 B 应该使用choose/when/otherwise,因为三个条件是互斥的优先级策略,只需要执行其中一个。
Q3:choose 中能否嵌套 if?
A:可以。
when内部可以嵌套其他动态 SQL 元素(包括if、foreach等),但通常不推荐过度嵌套,以免降低可读性。choose本身也可以嵌套在where、trim等容器内部使用。
Q4:如果没有 otherwise,且所有 when 都不满足,SQL 会变成什么样?
A:
choose节点不会输出任何内容。如果choose直接跟在WHERE后面,最终 SQL 会变成WHERE后面没有条件,导致语法错误。因此建议始终保留otherwise作为兜底分支,或者将choose嵌套在where元素内部,让where智能处理空内容的情况。
小结
choose/when/otherwise 是 MyBatis 中实现多分支互斥选择的利器,它将 Java 中冗长的 if-else if-else 链转换为声明式的 XML 配置,配合预编译参数占位符彻底消除了 SQL 注入风险。其核心语义是"只选其一",与多个独立 if 的"满足就拼"形成鲜明对比。典型应用场景包括:优先策略查询、状态枚举匹配、多条件兜底查询等。
下一节我们将学习 where 元素,它是解决动态查询中 WHERE 关键字和多余 AND/OR 问题的专业方案,也是 if 的最佳搭档。
下一章引子
当你使用 if 或 choose 构建动态查询时,是否还在为 WHERE 关键字和多余的 AND 烦恼?where 元素是 MyBatis 提供的智能包装器,它能自动插入 WHERE 并移除多余的 AND 或 OR,让你的动态查询代码彻底告别 WHERE 1=1 的妥协。继续阅读,掌握动态查询的最佳实践。