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

    • 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执行器类型
    • 分页插件

where

导学

本节你将掌握 MyBatis 中专门用于处理动态 WHERE 子句的元素 where。学习目标是:

  • 理解 where 元素解决的核心痛点(WHERE 关键字和多余 AND/OR)
  • 掌握 where 与 if 配合使用的标准模式
  • 理解 where 的内部实现原理(它是 trim 的特例)
  • 能够编写无 1=1 妥协的专业级动态查询

定义

where 是 MyBatis 动态 SQL 中专门用于智能处理 WHERE 子句的元素。它会自动完成两项工作:

  1. 智能插入 WHERE 关键字:只有当内部至少有一个条件满足时,才在 SQL 中插入 WHERE
  2. 智能移除多余的 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);

当前表数据:

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">

    <!-- 完全动态查询: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

查询结果集:

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

参数值: 白

查询结果集:

idnameagemajorscore
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

查询结果集:

idnameagemajorscore
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

查询结果集:

idnameagemajorscore
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在条件中间写 ANDwhere 只移除输出内容前缀的 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 如出一辙。继续阅读,掌握动态更新的最佳实践。

上一页
choose、when、otherwise
下一页
set