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

    • 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执行器类型
    • 分页插件

set

导学

本节你将掌握 MyBatis 中专门用于处理动态 SET 子句的元素 set。学习目标是:

  • 理解 set 元素解决的核心痛点(动态 UPDATE 时字段不确定导致的逗号问题)
  • 掌握 set 与 if 配合实现动态字段更新
  • 理解 set 的内部实现原理(它是 trim 的特例)
  • 能够编写安全、优雅的动态 UPDATE 语句

定义

set 是 MyBatis 动态 SQL 中专门用于智能处理 UPDATE 语句 SET 子句的元素。它会自动完成两项工作:

  1. 智能插入 SET 关键字:只有当内部至少有一个字段需要更新时,才在 SQL 中插入 SET
  2. 智能移除多余的逗号:如果最后一个输出的字段后面带有逗号,set 会自动将其移除

在 JDBC 编程中,实现动态更新通常需要复杂的字符串拼接:

String sql = "UPDATE student SET ";
boolean first = true;
if (name != null) {
    if (!first) sql += ", ";
    sql += "name = '" + name + "'";
    first = false;
}
if (major != null) {
    if (!first) sql += ", ";
    sql += "major = '" + major + "'";
    first = false;
}
// ... 还要处理一个字段都没传的情况

这种写法不仅冗长丑陋,而且每个字段都存在 SQL 注入风险。更麻烦的是,如果所有字段都为 null,SQL 会变成 UPDATE student SET 后面没有内容,导致语法错误。MyBatis 的 set 元素将动态更新逻辑声明式地表达在 XML 中,配合 #{ } 预编译占位符,既简洁又安全。


适用位置与核心属性

set 作为容器元素,只能用于 UPDATE 语句内部,嵌套一个或多个 if 等动态 SQL 元素。

属性是否必填说明
无—set 元素没有任何属性,它的行为是固定的

核心原理

set 智能去逗号流程图

set 的本质:trim 的特例

set 元素在 MyBatis 源码中的实现,本质上就是 trim 元素的一个预设特例:

<!-- set 的底层实现等价于: -->
<trim prefix="SET" suffixOverrides=",">
    ...
</trim>

这意味着 set 会:

  • 在内容前自动加上 SET 前缀
  • 移除内容末尾多余的逗号 ,

完整示例

场景说明

乐途学院的学生管理系统需要提供一个动态字段更新功能:管理员可以只更新学生的姓名,也可以只更新专业,也可以同时更新多个字段。要求使用 set 元素实现,确保无论传入哪些字段,生成的 UPDATE 语句都是语法正确的。

操作前的数据库表结构及初始数据

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">

    <!-- 动态更新学生信息:set 智能处理 SET 和逗号 -->
    <update id="updateStudentDynamic" parameterType="com.flying.entity.Student">
        UPDATE student
        <set>
            <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>
        </set>
        WHERE id = #{id}
    </update>

</mapper>

StudentMapper.java(接口)

package com.flying.mapper;

import com.flying.entity.Student;

public interface StudentMapper {
    int updateStudentDynamic(Student student);
}

实际执行结果

情况一:同时更新 name 和 major

测试代码:

Student student = new Student();
student.setId(1);
student.setName("大翔飞");
student.setMajor("人工智能");
mapper.updateStudentDynamic(student);

最终生成的 SQL 语句:

UPDATE student SET name = ?, major = ? WHERE id = ?

参数值: 大翔飞, 人工智能, 1

执行后表数据:

idnameagemajorscore
1大翔飞22人工智能95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

分析: 两个 if 满足,set 输出 SET 关键字。注意每个 if 内部都以逗号结尾,set 智能移除了最后一个字段后的多余逗号,最终 SQL 是干净的 SET name = ?, major = ?。

情况二:只更新 score

测试代码:

Student student = new Student();
student.setId(2);
student.setScore(91.0);
mapper.updateStudentDynamic(student);

最终生成的 SQL 语句:

UPDATE student SET score = ? WHERE id = ?

参数值: 91.0, 2

执行后表数据:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程91.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

分析: 只有 score 条件满足,set 自动插入 SET 并移除 score = ? 后面的逗号。如果只更新一个字段,逗号处理依然完美。

情况三:只传入 id,其他字段都为 null

测试代码:

Student student = new Student();
student.setId(3);
// name, age, major, score 全部为 null
mapper.updateStudentDynamic(student);

最终生成的 SQL 语句:

UPDATE student WHERE id = ?

执行结果: SQL 语法错误!UPDATE student WHERE id = ? 缺少 SET 子句。

分析: 所有 if 都不满足,set 内部没有任何内容输出,因此不输出 SET 关键字。最终 SQL 变成 UPDATE student WHERE id = ?,这是语法错误的。这说明单独使用 set 时,必须确保至少有一个字段会被更新,或者需要在 Java 代码中做前置校验。


易错场景 / 常见误区

误区错误示例后果正解
if 内部不以逗号结尾<if> name = #{name} </if>多个条件满足时,SQL 变成 SET name = ? age = ?,缺少逗号分隔,语法错误每个 if 内部都以逗号结尾,让 set 自动移除最后一个多余的逗号
所有字段都为 null只传 id,其他为 nullset 不输出任何内容,SQL 变成 UPDATE student WHERE id = ?,语法错误在 Java 代码中前置校验,确保至少有一个更新字段;或使用 choose 做兜底
在 set 后面手写 SETUPDATE student SET <set>...</set>语法错误,set 元素本身就会输出 SET删除手写的 SET,完全交给 set 元素处理
数值类型判断空字符串<if test="age != ''">age 是 Integer,与空字符串比较可能触发类型转换异常数值类型只判断 age != null
认为 set 能处理所有逗号在条件中间写逗号set 只移除输出内容后缀的逗号,中间的逗号不会动确保逗号只在每个字段赋值语句的末尾

面试考点

Q1:set 元素和 where 元素有什么相似之处?

A:set 和 where 都是 trim 元素的预设特例,设计思想完全一致。where 用于 SELECT 语句,自动插入 WHERE 并移除前缀多余的 AND/OR;set 用于 UPDATE 语句,自动插入 SET 并移除后缀多余的逗号。它们都解决了动态 SQL 中因为条件不确定而产生的语法边界问题。

Q2:set 的底层实现等价于哪个 trim 配置?

A:<trim prefix="SET" suffixOverrides=",">。它会在内容前添加 SET 前缀,并移除内容末尾多余的逗号。

Q3:如果所有更新字段都为 null,set 会生成什么样的 SQL?

A:所有 if 都不满足时,set 内部没有任何内容输出,因此不会输出 SET 关键字。最终 SQL 会变成 UPDATE student WHERE id = ?,这是语法错误的。因此实际开发中需要在 Java 层做前置校验,确保至少有一个字段需要更新,或者使用 choose 设置一个默认更新字段作为兜底。

Q4:以下代码有什么问题?

<set>
    <if test="name != null">name = #{name}</if>
    <if test="major != null">major = #{major}</if>
</set>

A:问题在于 if 内部的赋值语句末尾没有加逗号。当两个条件同时满足时,生成的 SQL 是 SET name = ? major = ?,两个字段之间缺少逗号分隔,导致语法错误。正确做法是在每个 if 内部都以逗号结尾:name = #{name}, 和 major = #{major},,让 set 自动移除最后一个多余的逗号。


小结

set 是 MyBatis 动态 SQL 中专门用于 UPDATE 语句的智能包装器,它与 if 配合实现了动态字段更新。set 自动解决了两个核心问题:① 何时插入 SET 关键字;② 如何移除最后一个字段后多余的逗号。它是 trim 元素的预设特例,底层等价于 <trim prefix="SET" suffixOverrides=",">。使用 set 后,动态 UPDATE 代码变得简洁、安全、专业。

需要注意的是,当所有更新字段都为 null 时,set 不会输出任何内容,会导致 SQL 语法错误。实际开发中应在 Java 层做前置校验,或设计兜底更新字段。

下一节我们将学习 trim 元素,它是 where 和 set 的底层通用实现,提供了最灵活的动态 SQL 前缀/后缀处理能力。


下一章引子

where 和 set 虽然强大,但它们是固定行为的预设方案。如果你需要自定义前缀、后缀,或者需要同时处理前缀和后缀的多余字符,该怎么办?trim 元素是 MyBatis 动态 SQL 中最灵活的容器,它是 where 和 set 的底层通用实现。掌握 trim,你就掌握了动态 SQL 的终极武器。继续阅读,解锁动态 SQL 的完全体。

上一页
where
下一页
foreach