乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

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
whentest是OGNL 表达式,与 if 的 test 语义相同。可以有多个 when,按顺序匹配,第一个满足条件的 when 被选中,其余忽略
otherwise无—默认分支,当所有 when 都不满足时执行。最多一个,且必须放在所有 when 之后

核心原理

多分支选择流程图

与 if 的本质区别


完整示例

场景说明

乐途学院的学生管理系统需要实现一个优先策略查询:管理员查询学生时,系统按照以下优先级策略返回结果:

  1. 如果传入了姓名(非空),则按姓名模糊查询
  2. 如果没有传入姓名但传入了专业,则按专业精确查询
  3. 如果姓名和专业都没有传入,则默认查询所有分数大于等于 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);

当前表数据:

idnameagemajorscore
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('%', ?, '%')

参数值: 小

查询结果集:

idnameagemajorscore
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 = ?

参数值: 计算机科学

查询结果集:

idnameagemajorscore
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

查询结果集:

idnameagemajorscore
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('%', ?, '%')

参数值: 大

查询结果集:

idnameagemajorscore
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 都不满足没有 otherwisechoose 不输出任何内容,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 的妥协。继续阅读,掌握动态查询的最佳实践。

上一页
if
下一页
where