MyBatis <where> 标签详解:让你的动态 SQL 更优雅、更安全
一、什么是 <where> 标签?
<where> 是 MyBatis 动态 SQL 中最常用的标签之一。它的核心作用是智能地构建 SQL 查询语句中的 WHERE 子句,主要解决两个痛点:
- 自动处理
WHERE关键字 :如果没有条件,不生成WHERE;如果有条件,自动添加WHERE。 - 自动去除多余的连接词 :自动去掉第一个条件前面多余的
AND或OR。
简单来说,<where> 标签让你彻底告别 WHERE 1=1 这种丑陋的写法。
二、基本语法
xml
<select id="xxx" resultType="xxx">
SELECT * FROM table_name
<where>
<!-- 动态条件 -->
<if test="条件1">
AND column1 = #{value1}
</if>
<if test="条件2">
AND column2 = #{value2}
</if>
</where>
</select>
2.1 工作原理
| 场景 | SQL 生成结果 |
|---|---|
| 没有任何条件成立 | SELECT * FROM table_name(无 WHERE) |
| 第一个条件成立 | 自动去掉开头的 AND,生成 WHERE column1 = ? |
| 多个条件成立 | 保留第一个条件的 AND,后续正常拼接 |
三、为什么需要 <where> 标签?
3.1 传统写法(不推荐)
在 <where> 出现之前,开发者通常使用 WHERE 1=1 来避免 AND 位置问题:
xml
<select id="selectByCondition" resultType="car">
SELECT * FROM t_car
WHERE 1=1
<if test="brand != null and brand != ''">
AND brand = #{brand}
</if>
<if test="carType != null and carType != ''">
AND car_type = #{carType}
</if>
</select>
问题:
WHERE 1=1在数据量大时会影响查询性能(虽然现代数据库优化器会忽略它,但不够优雅)。- 可读性差,显得不够专业。
3.2 使用 <where> 标签(推荐)
xml
<select id="selectByCondition" resultType="car">
SELECT * FROM t_car
<where>
<if test="brand != null and brand != ''">
AND brand = #{brand}
</if>
<if test="carType != null and carType != ''">
AND car_type = #{carType}
</if>
</where>
</select>
优点:
- 代码更清晰、更专业。
- 自动处理
WHERE和AND,减少手误。 - 没有多余的
1=1,SQL 更简洁。
四、<where> 标签的核心智能行为
4.1 自动去除开头的 AND 或 OR
示例:第一个条件成立
xml
<where>
<if test="brand != null">
AND brand = #{brand} <!-- 开头的 AND 会被自动去掉 -->
</if>
</where>
生成结果:WHERE brand = ?(而不是 WHERE AND brand = ?)
4.2 没有条件时,不生成 WHERE
xml
<where>
<if test="brand != null"> <!-- 条件为 false -->
AND brand = #{brand}
</if>
</where>
生成结果:SELECT * FROM t_car(无 WHERE)
4.3 多个条件
xml
<where>
<if test="brand != null">
AND brand = #{brand}
</if>
<if test="carType != null">
AND car_type = #{carType}
</if>
</where>
生成结果:
- 两个条件都成立:
WHERE brand = ? AND car_type = ? - 只有第二个成立:
WHERE car_type = ?(第一个条件的AND不存在,第二个的AND被自动去掉)
五、<where> 与 <if> 配合使用的最佳实践
5.1 条件判断标准模板
对于字符串类型,务必同时判断 null 和空字符串:
xml
<if test="brand != null and brand != ''">
AND brand = #{brand}
</if>
对于数值类型,只需判断 null:
xml
<if test="minPrice != null">
AND guide_price >= #{minPrice}
</if>
5.2 多条件组合示例
xml
<select id="selectCars" resultType="car">
SELECT * FROM t_car
<where>
<!-- 品牌模糊查询 -->
<if test="brand != null and brand != ''">
AND brand LIKE CONCAT('%', #{brand}, '%')
</if>
<!-- 价格区间 -->
<if test="minPrice != null">
AND guide_price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND guide_price <= #{maxPrice}
</if>
<!-- 车辆类型 -->
<if test="carType != null and carType != ''">
AND car_type = #{carType}
</if>
<!-- 生产日期范围 -->
<if test="startDate != null and startDate != ''">
AND produce_time >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND produce_time <= #{endDate}
</if>
</where>
</select>
六、<where> 的底层原理:基于 <trim> 的实现
<where> 标签本质上是一个 <trim> 标签的快捷方式。
<where> 等价于:
xml
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<!-- 动态条件 -->
</trim>
参数说明:
prefix="WHERE":如果内部有内容,前面加上WHERE。prefixOverrides="AND |OR ":去掉内部开头多余的AND或OR(注意OR后面有空格)。
七、常见错误与避坑指南
7.1 ❌ 错误:在 <where> 内部使用 OR 连接词
xml
<where>
<if test="brand != null">
OR brand = #{brand} <!-- 第一个条件是 OR,会被去掉,但逻辑可能错误 -->
</if>
</where>
如果第一个条件以 OR 开头,<where> 会去掉它,但生成的 SQL 可能不符合业务逻辑。建议统一使用 AND,并用括号包裹 OR 逻辑。
7.2 ❌ 错误:条件写在 <where> 标签外部
xml
SELECT * FROM t_car
WHERE
<if test="brand != null">
brand = #{brand}
</if>
如果条件不成立,SQL 会变成 SELECT * FROM t_car WHERE,直接报错。
正确做法 :所有条件必须写在 <where> 标签内部。
7.3 ❌ 错误:忘记判断空字符串
xml
<if test="brand != null">
AND brand = #{brand}
</if>
如果传入 brand = ""(空字符串),条件成立,SQL 变成 AND brand = '',这可能不是你想要的结果。
正确做法 :字符串类型同时判断 null 和空字符串。
7.4 ❌ 错误:<where> 与 WHERE 1=1 混用
xml
<where>
1=1 <!-- 这会导致 WHERE 1=1 始终成立,且无法去除 -->
<if test="brand != null">
AND brand = #{brand}
</if>
</where>
<where> 只处理 AND/OR,不会处理 1=1。混用会导致 SQL 变成 WHERE 1=1 AND brand = ?,虽然能运行,但失去了 <where> 的意义。
八、<where> vs 其他方案对比
| 方案 | SQL 生成 | 优点 | 缺点 |
|---|---|---|---|
WHERE 1=1 |
固定有 WHERE | 简单,不会出错 | 不优雅,影响可读性 |
手动拼接 WHERE |
需手动控制 | 完全控制 | 容易出错,代码冗余 |
<where> 标签 |
智能生成 | 优雅、安全、专业 | 需理解原理 |
<trim> 自定义 |
完全自定义 | 最灵活 | 配置稍复杂 |
九、完整实战示例
Mapper 接口:
java
List<Car> selectByCondition(@Param("brand") String brand,
@Param("carType") String carType,
@Param("minPrice") Double minPrice,
@Param("maxPrice") Double maxPrice);
XML 映射:
xml
<select id="selectByCondition" resultType="car">
SELECT * FROM t_car
<where>
<if test="brand != null and brand != ''">
AND brand LIKE CONCAT('%', #{brand}, '%')
</if>
<if test="carType != null and carType != ''">
AND car_type = #{carType}
</if>
<if test="minPrice != null">
AND guide_price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND guide_price <= #{maxPrice}
</if>
</where>
</select>
生成的 SQL:
-
传入
brand="宝马",carType=null,minPrice=null,maxPrice=null:sqlSELECT * FROM t_car WHERE brand LIKE CONCAT('%', '宝马', '%') -
传入
brand="宝马",carType="燃油车",minPrice=10,maxPrice=50:sqlSELECT * FROM t_car WHERE brand LIKE CONCAT('%', '宝马', '%') AND car_type = '燃油车' AND guide_price >= 10 AND guide_price <= 50 -
所有参数都为
null:sqlSELECT * FROM t_car
十、总结
<where>是动态 SQL 的核心标签之一,用于智能构建 WHERE 子句。- 自动处理
WHERE关键字:有条件加,无条件不加。 - 自动去除开头的
AND/OR:避免 SQL 语法错误。 - 永远配合
<if>使用 :条件判断逻辑写在<if>的test中。 - 字符串类型必须判断空字符串 :
!= null and != ''。 - 底层基于
<trim>实现:理解其原理有助于应对复杂场景。
<where> 标签让 MyBatis 的动态 SQL 更加优雅和专业。掌握它,你就能告别 WHERE 1=1 的尴尬写法,写出更高质量的代码。