MyBatis <where> 标签详解:让你的动态 SQL 更优雅、更安全

MyBatis <where> 标签详解:让你的动态 SQL 更优雅、更安全

一、什么是 <where> 标签?

<where> 是 MyBatis 动态 SQL 中最常用的标签之一。它的核心作用是智能地构建 SQL 查询语句中的 WHERE 子句,主要解决两个痛点:

  1. 自动处理 WHERE 关键字 :如果没有条件,不生成 WHERE;如果有条件,自动添加 WHERE。
  2. 自动去除多余的连接词 :自动去掉第一个条件前面多余的 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 &lt;= #{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 &lt;= #{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 &lt;= #{maxPrice}
        </if>
    </where>
</select>

生成的 SQL:

  • 传入 brand="宝马",carType=null,minPrice=null,maxPrice=null:

    sql 复制代码
    SELECT * FROM t_car WHERE brand LIKE CONCAT('%', '宝马', '%')
  • 传入 brand="宝马",carType="燃油车",minPrice=10,maxPrice=50:

    sql 复制代码
    SELECT * FROM t_car WHERE brand LIKE CONCAT('%', '宝马', '%') AND car_type = '燃油车' AND guide_price >= 10 AND guide_price <= 50
  • 所有参数都为 null:

    sql 复制代码
    SELECT * FROM t_car

十、总结

  1. <where> 是动态 SQL 的核心标签之一,用于智能构建 WHERE 子句。
  2. 自动处理 WHERE 关键字:有条件加,无条件不加。
  3. 自动去除开头的 AND/OR:避免 SQL 语法错误。
  4. 永远配合 <if> 使用 :条件判断逻辑写在 <if> 的 test 中。
  5. 字符串类型必须判断空字符串 :!= null and != ''。
  6. 底层基于 <trim> 实现:理解其原理有助于应对复杂场景。

<where> 标签让 MyBatis 的动态 SQL 更加优雅和专业。掌握它,你就能告别 WHERE 1=1 的尴尬写法,写出更高质量的代码。

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码4 天前
别再前后端各写一套表单校验了
java·后端
大勇前进4 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu4 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile4 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白804 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙4 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发4 天前
软件工程SOLID 五大设计原则
后端·软件工程