where
导学
本节你将掌握 MyBatis 中专门用于处理动态 WHERE 子句的元素 where。学习目标是:
- 理解
where元素解决的核心痛点(WHERE关键字和多余AND/OR) - 掌握
where与if配合使用的标准模式 - 理解
where的内部实现原理(它是trim的特例) - 能够编写无
1=1妥协的专业级动态查询
定义
where 是 MyBatis 动态 SQL 中专门用于智能处理 WHERE 子句的元素。它会自动完成两项工作:
- 智能插入
WHERE关键字:只有当内部至少有一个条件满足时,才在 SQL 中插入WHERE - 智能移除多余的
AND或OR:如果第一个满足的条件前带有AND或OR,where会自动将其移除
在 JDBC 编程或早期 MyBatis 使用中,开发者通常用 WHERE 1=1 来规避动态条件拼接问题:
String sql = "SELECT * FROM student WHERE 1=1"; // 丑陋的妥协
if (name != null) {
sql += " AND name LIKE ?";
}
if (major != null) {
sql += " AND major = ?";
}
WHERE 1=1 虽然能工作,但存在明显缺陷:① 语义不清晰,新开发者看到会困惑;② 可能影响数据库优化器的执行计划;③ 是一种"hack"式的写法,不够专业。where 元素从根本上解决了这个问题,让动态查询代码既正确又优雅。
适用位置与核心属性
where 作为容器元素,内部嵌套一个或多个 if、choose 等动态 SQL 元素。
| 属性 | 是否必填 | 说明 |
|---|---|---|
| 无 | — | where 元素没有任何属性,它的行为是固定的 |
核心原理
where 智能处理流程图
where 的本质:trim 的特例
where 元素在 MyBatis 源码中的实现,本质上就是 trim 元素的一个预设特例:
<!-- where 的底层实现等价于: -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
这意味着 where 会:
- 在内容前自动加上
WHERE前缀 - 移除内容开头多余的
AND或OR(注意包含后面的空格)
完整示例
场景说明
乐途学院的学生管理系统需要提供一个完全动态的组合查询功能:管理员可以按姓名模糊查、按专业精确查、按最低分数筛选,也可以任意组合这些条件,还可以什么都不填查询全部。要求使用 where 元素实现,彻底告别 WHERE 1=1。
操作前的数据库表结构及初始数据
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">
<!-- 完全动态查询:where 智能处理 WHERE 和 AND -->
<select id="findByDynamicCondition" resultType="Student"
parameterType="com.flying.entity.Student">
SELECT id, name, age, major, score
FROM student
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="major != null and major != ''">
AND major = #{major}
</if>
<if test="minScore != null">
AND score >= #{minScore}
</if>
</where>
</select>
</mapper>
StudentMapper.java(接口)
package com.flying.mapper;
import com.flying.entity.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findByDynamicCondition(Student condition);
}
实际执行结果
情况一:三个条件都传入
测试代码:
Student condition = new Student();
condition.setName("大");
condition.setMajor("计算机科学");
condition.setMinScore(90.0);
List<Student> list = mapper.findByDynamicCondition(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%') AND major = ? AND score >= ?
参数值: 大, 计算机科学, 90.0
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
分析: where 检测到内部有内容输出,自动插入 WHERE 关键字。第一个条件前的 AND 被智能移除,后续条件的 AND 保留。
情况二:只传入 name
测试代码:
Student condition = new Student();
condition.setName("白");
List<Student> list = mapper.findByDynamicCondition(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%')
参数值: 白
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
分析: 只有一个条件满足,where 自动插入 WHERE 并移除该条件前的 AND。SQL 干净无冗余。
情况三:只传入 minScore
测试代码:
Student condition = new Student();
condition.setMinScore(90.0);
List<Student> list = mapper.findByDynamicCondition(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE score >= ?
参数值: 90.0
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
分析: 前两个 if 都不满足,只有第三个 if 满足。where 智能处理,最终 SQL 只有一个 WHERE score >= ?,完美。
情况四:什么都不传入
测试代码:
Student condition = new Student();
List<Student> list = mapper.findByDynamicCondition(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student
查询结果集:
| 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 |
分析: 所有 if 都不满足,where 内部没有任何内容输出。where 智能判断为空,不输出 WHERE 关键字,最终 SQL 是一个干净的查询全表语句。
易错场景 / 常见误区
| 误区 | 错误示例 | 后果 | 正解 |
|---|---|---|---|
在 where 内部不写 AND | <if> name = #{name} </if> | 多个条件同时满足时,SQL 变成 WHERE name = ? major = ?,缺少连接词,语法错误 | 每个 if 内部的条件前都加上 AND 或 OR,让 where 自动处理第一个多余的 |
在 where 内部使用 OR 但不注意优先级 | <if> OR name = #{name} </if> | where 只能移除前缀的 OR,如果 OR 在中间会导致逻辑错误 | 统一使用 AND,需要 OR 时用括号明确优先级,或改用 trim 自定义 |
在 where 后面直接写静态条件 | WHERE <where>...</where> | 语法错误,where 元素本身就会输出 WHERE | 删除手写的 WHERE,完全交给 where 元素处理 |
认为 where 能移除所有位置的 AND | 在条件中间写 AND | where 只移除输出内容前缀的 AND/OR,中间的不会动 | 确保多余的 AND/OR 只在第一个条件前 |
在 where 内部嵌套 choose 但无 otherwise | <where><choose>...</choose></where> | 所有 when 不满足时 choose 无输出,where 也不输出,这是正确的;但如果期望有兜底则应该加 otherwise | 根据业务需求决定是否加 otherwise |
面试考点
Q1:where 元素和 WHERE 1=1 有什么区别?为什么推荐使用 where?
A:
WHERE 1=1是一种妥协方案,它通过永真条件来规避第一个AND的语法问题,但语义不清晰,可能影响数据库优化器的执行计划,且不够专业。where元素是 MyBatis 提供的原生解决方案,它能智能判断内部是否有条件输出:有则插入WHERE并移除第一个多余的AND/OR,没有则不输出WHERE。代码更优雅、语义更清晰、对优化器更友好。
Q2:where 元素在源码层面是如何实现的?
A:
where元素本质上是trim元素的一个预设特例。其底层等价于<trim prefix="WHERE" prefixOverrides="AND |OR ">。它利用trim的能力,在内容前添加WHERE前缀,并移除内容开头多余的AND或OR(注意包含后面的空格)。
Q3:以下代码有什么问题?
<where> <if test="name != null"> name = #{name} </if> <if test="major != null"> major = #{major} </if> </where>
A:问题在于
if内部的条件前没有加AND。当两个条件同时满足时,生成的 SQL 是WHERE name = ? major = ?,两个条件之间缺少连接词,导致语法错误。正确做法是在每个条件前都加上AND,让where自动处理第一个多余的:AND name = #{name}和AND major = #{major}。
Q4:where 能否处理 OR?
A:可以。
where的prefixOverrides配置是"AND |OR ",所以它能同时移除前缀的AND和OR。但需要注意:如果多个条件混合使用AND和OR,必须用小括号明确优先级,否则可能产生逻辑错误。例如AND (name = ? OR major = ?)是安全的,但散落的AND和OR可能导致歧义。
小结
where 是 MyBatis 动态查询中最常用的容器元素,它与 if 配合构成了动态条件查询的标准模式。where 自动解决了两个核心问题:① 何时插入 WHERE 关键字;② 如何移除第一个条件前多余的 AND/OR。它是 trim 元素的预设特例,底层实现简洁而强大。使用 where 后,开发者可以彻底告别 WHERE 1=1 的妥协写法,编写出专业、优雅的动态查询代码。
下一节我们将学习 set 元素,它是 where 在 UPDATE 语句中的对应物,专门解决动态更新时 SET 关键字和多余逗号的问题。
下一章引子
动态查询的问题解决了,那动态更新呢?当你需要根据不同的传入参数动态更新学生的不同字段时,如何优雅地处理 SET 关键字和字段之间多余的逗号?set 元素正是为 UPDATE 语句而生的智能包装器,它的工作原理与 where 如出一辙。继续阅读,掌握动态更新的最佳实践。