MyBatis <trim> 标签完全解析:动态 SQL 的终极武器

MyBatis <trim> 标签完全解析:动态 SQL 的终极武器

一、什么是 <trim> 标签?

<trim> 是 MyBatis 动态 SQL 中最灵活、最强大的标签之一。它是 <where><set> 标签的底层实现基础,可以理解为它们的"超集"或"通用版本"。

如果说 <where> 是专为 WHERE 子句设计的"专用工具",那么 <trim> 就是一把可以自由裁剪、拼接 SQL 片段的"瑞士军刀"。

核心功能:

  1. 前缀添加prefix):在内容前添加指定字符串。
  2. 后缀添加suffix):在内容后添加指定字符串。
  3. 前缀覆盖prefixOverrides):去掉内容开头指定的字符串。
  4. 后缀覆盖suffixOverrides):去掉内容末尾指定的字符串。

二、基本语法

xml 复制代码
<trim prefix="前缀字符串" 
      suffix="后缀字符串" 
      prefixOverrides="要去掉的前缀" 
      suffixOverrides="要去掉的后缀">
    
    动态 SQL 内容
    
</trim>

属性说明

属性 作用 示例值 说明
prefix 如果内容不为空,在前面添加此字符串 WHERESET( 相当于拼接前缀
suffix 如果内容不为空,在后面添加此字符串 )AND 相当于拼接后缀
prefixOverrides 去掉内容开头 的指定字符串(可多个,用 ` ` 分隔) `AND
suffixOverrides 去掉内容末尾 的指定字符串(可多个,用 ` ` 分隔) ,、`AND

关键规则

  • prefixsuffix 只有内部内容不为空时才会生效。
  • prefixOverridessuffixOverrides 会忽略大小写 (如 andANDAnd 都会被匹配)。
  • 多个覆盖值用 |(竖线)分隔。

三、用 <trim> 实现 <where><set>

理解了 <trim>,你就从根本上理解了 <where><set> 的原理。

3.1 用 <trim> 实现 <where>

<where> 等价于以下 <trim> 配置:

xml 复制代码
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <!-- 动态条件 -->
    <if test="brand != null">AND brand = #{brand}</if>
    <if test="carType != null">AND car_type = #{carType}</if>
</trim>

工作原理

  1. 如果有条件成立,内容不为空,自动添加 WHERE 前缀。
  2. 去掉内容开头多余的 ANDOR(注意 AND |OR 后面有空格)。
  3. 生成的 SQL:WHERE brand = ? AND car_type = ?

3.2 用 <trim> 实现 <set>

<set> 等价于以下 <trim> 配置:

xml 复制代码
<trim prefix="SET" suffixOverrides=",">
    <if test="brand != null">brand = #{brand},</if>
    <if test="carType != null">car_type = #{carType},</if>
</trim>

工作原理

  1. 如果有条件成立,内容不为空,自动添加 SET 前缀。
  2. 去掉内容末尾多余的逗号。
  3. 生成的 SQL:SET brand = ? , car_type = ?(末尾逗号被自动去除)。

四、典型使用场景

场景 1:自定义 WHERE 子句(更复杂的条件)

当标准的 <where> 标签无法满足需求时(例如需要动态添加括号、多组 OR 条件),可以使用 <trim> 进行精确控制。

xml 复制代码
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="brand != null">
        AND brand = #{brand}
    </if>
    <if test="keyword != null and keyword != ''">
        AND (brand LIKE CONCAT('%', #{keyword}, '%') 
             OR car_type LIKE CONCAT('%', #{keyword}, '%'))
    </if>
</trim>

场景 2:动态 INSERT 语句(只插入非空字段)

当需要根据传入对象动态决定插入哪些字段时,<trim> 非常有用。

xml 复制代码
<insert id="insertSelective">
    INSERT INTO t_car
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="carNum != null and carNum != ''">car_num,</if>
        <if test="brand != null and brand != ''">brand,</if>
        <if test="guidePrice != null">guide_price,</if>
        <if test="produceTime != null and produceTime != ''">produce_time,</if>
        <if test="carType != null and carType != ''">car_type,</if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="carNum != null and carNum != ''">#{carNum},</if>
        <if test="brand != null and brand != ''">#{brand},</if>
        <if test="guidePrice != null">#{guidePrice},</if>
        <if test="produceTime != null and produceTime != ''">#{produceTime},</if>
        <if test="carType != null and carType != ''">#{carType},</if>
    </trim>
</insert>

场景 3:动态 UPDATE 语句(选择性更新)

虽然 <set> 可以满足大部分场景,但 <trim> 在复杂更新场景下更灵活。

xml 复制代码
<update id="updateSelective">
    UPDATE t_car
    <trim prefix="SET" suffixOverrides=",">
        <if test="brand != null and brand != ''">brand = #{brand},</if>
        <if test="guidePrice != null">guide_price = #{guidePrice},</if>
        <if test="carType != null and carType != ''">car_type = #{carType},</if>
        <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
    </trim>
    WHERE id = #{id}
</update>

场景 4:动态 IN 查询时加括号

配合 <foreach>,使用 <trim> 可以更清晰地控制 IN 子句:

xml 复制代码
<trim prefix="WHERE id IN (" suffix=")" prefixOverrides=",">
    <foreach collection="ids" item="id">
        #{id},
    </foreach>
</trim>

或者更常见的方式(直接用 open / close 属性),但 <trim> 提供了一种替代思路。

场景 5:多表 JOIN 条件

在复杂查询中,可以使用 <trim> 动态拼接 JOIN 条件:

xml 复制代码
SELECT * FROM t_car c
<trim prefix="LEFT JOIN t_engine e ON c.id = e.car_id" prefixOverrides="AND">
    <if test="engineType != null">
        AND e.type = #{engineType}
    </if>
</trim>

五、<trim> 与其他标签的对比

标签 本质 适用场景 灵活性
<where> <trim> 的快捷方式 简单的 WHERE 子句构建 低(专门场景)
<set> <trim> 的快捷方式 简单的 SET 子句构建 低(专门场景)
<trim> 通用处理器 复杂的 SQL 片段拼接 极高(完全自定义)

选择建议

  • 绝大多数场景用 <where><set> 足够,它们更简洁。
  • 只有当需要同时处理前缀、后缀、前覆盖、后覆盖中的两个以上时,才使用 <trim>

六、<trim> 背后的设计思想

<trim> 实际上是一个链式处理器,它会依次对内部的 SQL 片段执行以下操作:

  1. 检查内容是否为空 :如果为空,整个 <trim> 不输出任何内容。
  2. 应用 prefixOverrides:匹配并移除内容开头的指定字符串。
  3. 应用 suffixOverrides:匹配并移除内容末尾的指定字符串。
  4. 添加 prefix:在内容前面加上指定字符串。
  5. 添加 suffix:在内容后面加上指定字符串。

核心prefixOverridessuffixOverrides 是在添加前缀和后缀之前执行的。

七、常见错误与避坑指南

1. prefixOverrides 后面的空格陷阱

xml 复制代码
<!-- ❌ 错误:AND 后面没有空格,无法正确匹配 -->
<trim prefix="WHERE" prefixOverrides="AND">
    <if test="brand != null">AND brand = #{brand}</if>
</trim>

<!-- ✅ 正确:AND 后面有空格(标准写法) -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="brand != null">AND brand = #{brand}</if>
</trim>

原因 :条件中的 AND 后面通常跟着一个空格("AND "),所以 prefixOverrides 必须包含空格才能正确匹配去除。

2. suffixOverrides 多个值使用 | 分隔

xml 复制代码
<!-- 同时去掉 AND 和 OR -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">

注意| 两边不要有空格(除非你想匹配空格本身)。

3. 混合使用 prefixOverridesprefix

先去除再添加,顺序很重要。

xml 复制代码
<trim prefix="SET" prefixOverrides="," suffixOverrides=",">
    <!-- 内容 -->
</trim>

如果内容为空,SET 不会添加。如果有内容,先去除末尾逗号,再添加 SET

八、完整实战示例

复杂的多表联查动态条件

xml 复制代码
<select id="queryCarWithParts" resultType="car">
    SELECT c.*, p.* 
    FROM t_car c
    LEFT JOIN t_part p ON c.id = p.car_id
    
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <!-- 品牌模糊查询 -->
        <if test="brand != null and brand != ''">
            AND c.brand LIKE CONCAT('%', #{brand}, '%')
        </if>
        
        <!-- 价格区间 -->
        <if test="minPrice != null">
            AND c.guide_price >= #{minPrice}
        </if>
        <if test="maxPrice != null">
            AND c.guide_price &lt;= #{maxPrice}
        </if>
        
        <!-- 配件名称(条件组) -->
        <if test="partName != null and partName != ''">
            AND p.name LIKE CONCAT('%', #{partName}, '%')
        </if>
        
        <!-- 动态排序(必须使用 ${}) -->
        <if test="orderColumn != null and orderColumn != ''">
            ORDER BY ${orderColumn}
            <if test="orderDirection != null and orderDirection != ''">
                ${orderDirection}
            </if>
        </if>
    </trim>
</select>

九、总结

| 要点 | 说明 |
|:--------------------|:-----------------------------------------------------------------|---------|
| 核心作用 | 动态拼接 SQL 片段,智能处理前缀、后缀及多余字符 |
| 本质 | <where><set> 的底层实现,功能更强大 |
| 常用属性 | prefixsuffixprefixOverridessuffixOverrides |
| <where> 的关系 | <where><trim> 的快捷方式(`prefix="WHERE" prefixOverrides="AND | OR "`) |
| <set> 的关系 | <set><trim> 的快捷方式(prefix="SET" suffixOverrides=",") |
| 使用原则 | 简单场景用 <where>/<set>,复杂自定义场景用 <trim> |

最佳实践

  1. 能用 <where>/<set> 就用它们,代码更简洁。
  2. 遇到需要同时操作前缀、后缀、前覆盖、后覆盖中的两种以上时,改用 <trim>
  3. 在动态 INSERT 场景中,<trim> 是无可替代的最佳选择。
  4. 注意 prefixOverrides 后面通常需要带空格(如 AND |OR )。

掌握了 <trim> 标签,你就真正掌握了 MyBatis 动态 SQL 的核心精髓,可以在任意复杂场景下轻松构建 SQL 语句。

相关推荐
名字还没想好☜5 分钟前
Java 用 LinkedHashMap 三行实现 LRU 缓存:accessOrder、removeEldestEntry 与线程安全
java·后端·安全·spring·缓存
谢亮_vipxieliang19 分钟前
ValidX vs Google Guava Preconditions:验证 vs 断言
java·spring boot·后端·spring cloud·hibernate·guava
mldong8 小时前
C# 开发者也有自己的轻量工作流引擎了:NuGet 装包,5 分钟跑通一条审批流
后端·c#·.net
爱学堂IT分享10 小时前
鹤翔老师pmp项目管理培训认证考试课 (直播录播题库送学时证明)【共58课时】-51CTO学堂
后端
王的宝库10 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
码事漫谈11 小时前
类型即世界:一个 C++ 程序员的本体论入门
后端
Rain的Java大神之路11 小时前
JavaWeb开发如何解决跨域问题
java·前端·后端·nginx·web安全·面试·运维开发
IT_陈寒14 小时前
Python的多线程居然是个假把式?搞清GIL让我少熬三天夜
前端·人工智能·后端
明月_清风14 小时前
位图与布隆过滤器:海量数据下的"存在性判断"艺术
前端·后端·算法
明月_清风14 小时前
Hash 表从入门到精通:Go 实战与工程细节
前端·后端·算法