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 语句。

相关推荐
XPoet1 小时前
AI 编程工程化:实战——从 0 到 1 搭建 AI 编程工作流
前端·后端·ai编程
掘金者阿豪1 小时前
向量数据库不是终点,企业AI真正需要的是融合数据库
后端
程序员cxuan1 小时前
OpenAI Linux 版来了!
人工智能·后端·程序员
大黄评测1 小时前
Angular 表单:响应式表单高级用法,Typed Forms 类型化表单实战
后端
大勇前进1 小时前
Angular 变更检测深度讲解:OnPush 策略什么时候用、踩过哪些坑
后端
拾光师2 小时前
Python 解析 JSON 日志:从一行数据到一份报告
后端
小强19882 小时前
RxJS 在 Angular 项目最佳实践:彻底告别内存泄漏,用好 asyncPipe
后端
大白802 小时前
Angular 路由进阶:路由守卫、懒加载、动态路由、路由传参避坑合集
后端
大黄评测2 小时前
SignalStore vs NgRx:企业项目状态管理该怎么选,不要盲目上大库
后端