trim
导学
本节你将掌握 MyBatis 动态 SQL 中最灵活的容器元素 trim。学习目标是:
- 理解
trim作为where和set底层通用实现的地位 - 掌握
prefix、prefixOverrides、suffix、suffixOverrides四个核心属性 - 能够使用
trim实现where和set无法覆盖的自定义场景 - 能够编写自定义前缀后缀处理的动态 SQL(如批量 INSERT 的括号处理)
定义
trim 是 MyBatis 动态 SQL 中最灵活、最通用的容器元素。它允许开发者自定义:
- 前缀(prefix):当内部有内容输出时,在内容前自动添加指定的字符串
- 后缀(suffix):当内部有内容输出时,在内容后自动添加指定的字符串
- 前缀覆盖(prefixOverrides):移除内容开头指定的字符序列
- 后缀覆盖(suffixOverrides):移除内容末尾指定的字符序列
where 和 set 都是 trim 的预设特例:
| 元素 | 等价 trim 配置 |
|---|---|
where | <trim prefix="WHERE" prefixOverrides="AND |OR "> |
set | <trim prefix="SET" suffixOverrides=","> |
当 where 和 set 的固定行为无法满足需求时(例如需要同时处理前缀和后缀、需要自定义前缀后缀内容),就需要使用 trim 来自定义。
适用位置与核心属性
trim 作为通用容器元素,可以嵌套在 select、insert、update、delete 语句内部,包裹 if、choose、foreach 等动态 SQL 子元素。
| 属性 | 是否必填 | 说明 |
|---|---|---|
prefix | 否 | 当内部有内容输出时,在内容前添加的前缀字符串 |
prefixOverrides | 否 | 移除内容开头匹配的字符序列,多个用 | 分隔,注意末尾空格 |
suffix | 否 | 当内部有内容输出时,在内容后添加的后缀字符串 |
suffixOverrides | 否 | 移除内容末尾匹配的字符序列,多个用 | 分隔 |
核心原理
trim 通用处理流程图
trim 处理顺序
处理顺序非常重要:先移除覆盖字符,再添加前缀后缀。这意味着 prefixOverrides 移除的是内部内容原始前缀,而不是添加了 prefix 之后的前缀。
完整示例
场景说明
乐途学院的学生管理系统需要实现以下三个动态 SQL 场景,这些场景都需要 trim 的灵活定制能力:
- 自定义 WHERE 容器:实现一个比
where更严格的容器,要求同时移除前缀的AND/OR和后缀的逗号 - 动态 INSERT 值列表:批量插入时,动态生成
(字段1, 字段2) VALUES (值1, 值2)的括号包裹 - 动态 SET 增强版:更新时同时处理前缀
SET和后缀逗号,并支持WHERE条件的动态拼接
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前表数据:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整的映射文件片段
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flying.mapper.StudentMapper">
<!-- 示例1:用 trim 自定义 where 行为 -->
<select id="findByTrim" resultType="Student"
parameterType="com.flying.entity.Student">
SELECT id, name, age, major, score
FROM student
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="major != null and major != ''">
AND major = #{major}
</if>
<if test="minScore != null">
AND score >= #{minScore}
</if>
</trim>
</select>
<!-- 示例2:用 trim 实现动态 INSERT 字段列表和值列表 -->
<insert id="insertStudentDynamic" parameterType="com.flying.entity.Student"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO student
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">name,</if>
<if test="age != null">age,</if>
<if test="major != null">major,</if>
<if test="score != null">score,</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">#{name},</if>
<if test="age != null">#{age},</if>
<if test="major != null">#{major},</if>
<if test="score != null">#{score},</if>
</trim>
</insert>
<!-- 示例3:用 trim 实现增强版动态 UPDATE -->
<update id="updateStudentTrim" parameterType="com.flying.entity.Student">
UPDATE student
<trim prefix="SET" suffixOverrides=",">
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="major != null and major != ''">
major = #{major},
</if>
<if test="score != null">
score = #{score},
</if>
</trim>
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null and name != ''">
AND name = #{name}
</if>
</trim>
</update>
</mapper>
StudentMapper.java(接口)
package com.flying.mapper;
import com.flying.entity.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findByTrim(Student condition);
int insertStudentDynamic(Student student);
int updateStudentTrim(Student student);
}
实际执行结果
示例1:trim 自定义 WHERE(name 和 minScore 同时传入)
测试代码:
Student condition = new Student();
condition.setName("大");
condition.setMinScore(90.0);
List<Student> list = mapper.findByTrim(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%') AND score >= ?
参数值: 大, 90.0
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
分析: 这个 trim 配置 prefix="WHERE" prefixOverrides="AND |OR " 与 where 元素完全等价。第一个条件前的 AND 被移除,后续条件的 AND 保留。
示例2:trim 动态 INSERT(只插入 name 和 score)
测试代码:
Student student = new Student();
student.setName("乐途");
student.setScore(99.0);
mapper.insertStudentDynamic(student);
最终生成的 SQL 语句:
INSERT INTO student ( name, score ) VALUES ( ?, ? )
参数值: 乐途, 99.0
执行后表数据:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
| 6 | 乐途 | null | null | 99.00 |
分析: 两个 trim 容器分别处理字段列表和值列表。prefix="(" 和 suffix=")" 自动添加括号,suffixOverrides="," 移除最后一个字段后的逗号。这是 where 和 set 无法实现的自定义场景。
示例3:trim 增强版 UPDATE(更新 name,按 id 定位)
测试代码:
Student student = new Student();
student.setId(3);
student.setName("崔乐途");
mapper.updateStudentTrim(student);
最终生成的 SQL 语句:
UPDATE student SET name = ? WHERE id = ?
参数值: 崔乐途, 3
执行后表数据:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 崔乐途 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
分析: 第一个 trim 实现 set 的功能(prefix="SET" suffixOverrides=","),第二个 trim 实现 where 的功能(prefix="WHERE" prefixOverrides="AND |OR ")。一个 update 语句中使用了两个 trim,展示了其通用性和灵活性。
易错场景 / 常见误区
| 误区 | 错误示例 | 后果 | 正解 |
|---|---|---|---|
prefixOverrides 忘记末尾空格 | prefixOverrides="AND|OR" | 无法正确移除 AND (带空格),因为匹配的是 AND 而不是 AND | prefixOverrides="AND |OR ",注意空格 |
| 混淆处理顺序 | 以为先加 prefix 再移除 | 实际是先移除 overrides,再添加 prefix/suffix | 记住顺序:先移除,后添加 |
prefixOverrides 使用正则 | prefixOverrides="AND.*" | prefixOverrides 是字符序列匹配,不是正则表达式 | 精确写出要移除的字符序列,如 AND |OR |
以为 trim 可以嵌套任意层 | 过度嵌套 trim | 可读性极差,维护困难 | 保持层次清晰,一般不超过两层嵌套 |
在 prefixOverrides 中写 , | prefixOverrides="," | 语法上可行,但通常逗号是后缀问题,应该用 suffixOverrides | 根据实际场景选择 prefixOverrides 或 suffixOverrides |
面试考点
Q1:trim 与 where、set 是什么关系?
A:
where和set都是trim的预设特例。where等价于<trim prefix="WHERE" prefixOverrides="AND |OR ">,set等价于<trim prefix="SET" suffixOverrides=",">。trim是通用底层实现,提供了最灵活的前缀/后缀及覆盖字符处理能力;where和set是常用场景的便捷封装。
Q2:trim 的四个属性处理顺序是什么?
A:处理顺序是:① 先解析内部子节点得到原始输出内容;② 应用
prefixOverrides移除内容前缀匹配的字符序列;③ 应用suffixOverrides移除内容后缀匹配的字符序列;④ 添加prefix到内容最前面;⑤ 添加suffix到内容最后面。关键是"先移除,后添加"。
Q3:为什么 prefixOverrides="AND |OR " 中 | 前后要有空格?
A:
prefixOverrides中使用|分隔多个要移除的字符序列。AND |OR表示要移除"AND "或"OR "。注意"AND "和"OR "末尾都包含一个空格,这是为了匹配 SQL 中AND name = ?这种写法中的AND。如果写成AND|OR,则只会匹配AND或OR而不包含后面的空格,导致移除后留下一个前导空格,虽然通常不影响执行,但不够严谨。
Q4:以下 trim 配置实现了什么效果?
<trim prefix="VALUES" prefixOverrides="," suffix=")" suffixOverrides=",">
A:这个配置会在内容前添加
VALUES,移除内容前缀的逗号,在内容后添加),并移除内容后缀的逗号。这种配置常用于动态 INSERT 语句中处理INSERT INTO table (...) VALUES (...)的场景,确保括号内的字段列表和值列表前后都没有多余的逗号。
小结
trim 是 MyBatis 动态 SQL 中最灵活、最通用的容器元素,它是 where 和 set 的底层实现。通过 prefix、suffix、prefixOverrides、suffixOverrides 四个属性,trim 可以实现任意自定义的前缀后缀处理逻辑。掌握 trim 后,你不仅理解了 where 和 set 的工作原理,还能应对它们无法覆盖的复杂场景(如动态 INSERT 的括号包裹、同时处理前缀和后缀等)。
使用 trim 时需要注意:① 处理顺序是"先移除 overrides,后添加 prefix/suffix";② prefixOverrides 中多个序列用 | 分隔,注意包含末尾空格;③ 不要过度嵌套,保持代码可读性。
下一节我们将学习 foreach 元素,它是处理集合遍历的核心元素,广泛应用于 IN 条件查询、批量插入、批量更新等场景。
下一章引子
当你需要一次性查询多个 ID 对应的学生记录,或者需要批量插入多条学生数据时,简单的 if 和 trim 已经无法满足需求。foreach 元素是 MyBatis 中专门用于遍历集合的动态 SQL 元素,它能将 Java 中的 List、Array、Map 展开为 SQL 中的多个值或记录。继续阅读,掌握批量操作的核心技术。