乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

trim

导学

本节你将掌握 MyBatis 动态 SQL 中最灵活的容器元素 trim。学习目标是:

  • 理解 trim 作为 where 和 set 底层通用实现的地位
  • 掌握 prefix、prefixOverrides、suffix、suffixOverrides 四个核心属性
  • 能够使用 trim 实现 where 和 set 无法覆盖的自定义场景
  • 能够编写自定义前缀后缀处理的动态 SQL(如批量 INSERT 的括号处理)

定义

trim 是 MyBatis 动态 SQL 中最灵活、最通用的容器元素。它允许开发者自定义:

  1. 前缀(prefix):当内部有内容输出时,在内容前自动添加指定的字符串
  2. 后缀(suffix):当内部有内容输出时,在内容后自动添加指定的字符串
  3. 前缀覆盖(prefixOverrides):移除内容开头指定的字符序列
  4. 后缀覆盖(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 的灵活定制能力:

  1. 自定义 WHERE 容器:实现一个比 where 更严格的容器,要求同时移除前缀的 AND/OR 和后缀的逗号
  2. 动态 INSERT 值列表:批量插入时,动态生成 (字段1, 字段2) VALUES (值1, 值2) 的括号包裹
  3. 动态 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);

当前表数据:

idnameagemajorscore
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

查询结果集:

idnameagemajorscore
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

执行后表数据:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00
6乐途nullnull99.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

执行后表数据:

idnameagemajorscore
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 中的多个值或记录。继续阅读,掌握批量操作的核心技术。

上一页
foreach
下一页
bind