MyBatis <trim> 标签完全解析:动态 SQL 的终极武器
一、什么是 <trim> 标签?
<trim> 是 MyBatis 动态 SQL 中最灵活、最强大的标签之一。它是 <where> 和 <set> 标签的底层实现基础,可以理解为它们的"超集"或"通用版本"。
如果说 <where> 是专为 WHERE 子句设计的"专用工具",那么 <trim> 就是一把可以自由裁剪、拼接 SQL 片段的"瑞士军刀"。
核心功能:
- 前缀添加 (
prefix):在内容前添加指定字符串。 - 后缀添加 (
suffix):在内容后添加指定字符串。 - 前缀覆盖 (
prefixOverrides):去掉内容开头指定的字符串。 - 后缀覆盖 (
suffixOverrides):去掉内容末尾指定的字符串。
二、基本语法
xml
<trim prefix="前缀字符串"
suffix="后缀字符串"
prefixOverrides="要去掉的前缀"
suffixOverrides="要去掉的后缀">
动态 SQL 内容
</trim>
属性说明
| 属性 | 作用 | 示例值 | 说明 |
|---|---|---|---|
prefix |
如果内容不为空,在前面添加此字符串 | WHERE、SET、( |
相当于拼接前缀 |
suffix |
如果内容不为空,在后面添加此字符串 | )、AND |
相当于拼接后缀 |
prefixOverrides |
去掉内容开头 的指定字符串(可多个,用 ` | ` 分隔) | `AND |
suffixOverrides |
去掉内容末尾 的指定字符串(可多个,用 ` | ` 分隔) | ,、`AND |
关键规则:
prefix和suffix只有内部内容不为空时才会生效。prefixOverrides和suffixOverrides会忽略大小写 (如and、AND、And都会被匹配)。- 多个覆盖值用
|(竖线)分隔。
三、用 <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>
工作原理:
- 如果有条件成立,内容不为空,自动添加
WHERE前缀。 - 去掉内容开头多余的
AND或OR(注意AND |OR后面有空格)。 - 生成的 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>
工作原理:
- 如果有条件成立,内容不为空,自动添加
SET前缀。 - 去掉内容末尾多余的逗号。
- 生成的 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 片段执行以下操作:
- 检查内容是否为空 :如果为空,整个
<trim>不输出任何内容。 - 应用
prefixOverrides:匹配并移除内容开头的指定字符串。 - 应用
suffixOverrides:匹配并移除内容末尾的指定字符串。 - 添加
prefix:在内容前面加上指定字符串。 - 添加
suffix:在内容后面加上指定字符串。
核心 :prefixOverrides 和 suffixOverrides 是在添加前缀和后缀之前执行的。
七、常见错误与避坑指南
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. 混合使用 prefixOverrides 和 prefix
先去除再添加,顺序很重要。
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 <= #{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> 的底层实现,功能更强大 |
| 常用属性 | prefix、suffix、prefixOverrides、suffixOverrides |
| 与 <where> 的关系 | <where> 是 <trim> 的快捷方式(`prefix="WHERE" prefixOverrides="AND | OR "`) |
| 与 <set> 的关系 | <set> 是 <trim> 的快捷方式(prefix="SET" suffixOverrides=",") |
| 使用原则 | 简单场景用 <where>/<set>,复杂自定义场景用 <trim> |
最佳实践:
- 能用
<where>/<set>就用它们,代码更简洁。 - 遇到需要同时操作前缀、后缀、前覆盖、后覆盖中的两种以上时,改用
<trim>。 - 在动态
INSERT场景中,<trim>是无可替代的最佳选择。 - 注意
prefixOverrides后面通常需要带空格(如AND |OR)。
掌握了 <trim> 标签,你就真正掌握了 MyBatis 动态 SQL 的核心精髓,可以在任意复杂场景下轻松构建 SQL 语句。