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

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

@Update

导学

本节学习目标:

  • 掌握 @Update 注解的基本用法,能够替代 XML 中的 <update> 标签完成数据更新
  • 理解 @Update 返回影响行数的业务意义,学会利用返回值做乐观校验
  • 明确注解更新与 XML 更新在 SQL 编写和参数绑定上的等价性
  • 了解注解方式下动态更新(选择性更新字段)的实现思路

定义

@Update 是 MyBatis 提供的核心更新注解,标注在 Mapper 接口的方法上,用于声明 UPDATE SQL 语句。它对应 XML 映射文件中的 <update> 元素。

痛点解决:在纯注解开发中,开发者无需维护 XML 即可声明更新逻辑。对于字段固定的全量更新,@Update 一行注解即可完成映射;同时其返回的 int 影响行数可直接用于判断记录是否存在、是否更新成功,是业务层做乐观锁或存在性校验的便捷手段。

注解方式 vs XML 方式对比

对比维度@Update 注解方式XML <update> 方式
固定字段更新一行注解,简洁需写 XML 标签,相对繁琐
动态字段更新需借助 <script> 或 Provider原生支持 <set>、<if> 标签
影响行数获取接口返回 int 即可同样返回 int,行为一致
维护性短 SQL 集中管理长 SQL、动态 SQL 更易维护

适用场景建议:字段明确的全量更新优先使用 @Update;需要动态判断哪些字段参与更新(如用户只修改了昵称,不修改手机号)时,建议使用 XML 的 <set> 或 @UpdateProvider。

适用位置与核心属性

@Update 只能标注在 Mapper 接口的方法 上。

属性类型必填说明
valueString是要执行的 UPDATE SQL 语句,支持 #{} 引用参数

核心原理

MyBatis 解析 @Update 后注册为 MappedStatement,语句类型标记为 UPDATE。执行时,MyBatis 将 #{} 替换为预编译参数,调用 JDBC 的 Statement.executeUpdate()。数据库返回的影响行数会被 MyBatis 直接透传给 Mapper 接口的返回值(声明为 int 或 Integer)。

完整示例

场景说明

乐途公司学生管理系统需要实现:根据学生 ID 修改其专业和分数;同时需要知道该学生是否存在,若影响行数为 0 则提示"学生不存在"。

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

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

完整的注解接口代码

实体类

package com.flying.entity;

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String major;
    private Double score;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getMajor() { return major; }
    public void setMajor(String major) { this.major = major; }
    public Double getScore() { return score; }
    public void setScore(Double score) { this.score = score; }
}

Mapper 接口

package com.flying.mapper;

import com.flying.entity.Student;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;

public interface StudentMapper {

    /**
     * 根据 ID 更新学生的专业和分数
     * 返回影响行数
     */
    @Update("UPDATE student SET major = #{major}, score = #{score} WHERE id = #{id}")
    int updateMajorAndScoreById(@Param("id") Integer id,
                                @Param("major") String major,
                                @Param("score") Double score);

    /**
     * 传入对象进行全量字段更新(除 ID 外)
     */
    @Update("UPDATE student SET name = #{name}, age = #{age}, major = #{major}, score = #{score} WHERE id = #{id}")
    int updateStudent(Student student);
}

测试调用代码

package com.flying.test;

import com.flying.entity.Student;
import com.flying.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class UpdateTest {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();

        StudentMapper mapper = session.getMapper(StudentMapper.class);

        // 1. 多参数更新:将大翔的专业改为"人工智能",分数改为 96.0
        System.out.println("=== 多参数更新 ===");
        int rows1 = mapper.updateMajorAndScoreById(1, "人工智能", 96.0);
        System.out.println("影响行数: " + rows1);
        if (rows1 == 0) {
            System.out.println("警告:该学生不存在,更新失败!");
        }

        // 2. 对象参数更新:修改白歌的全部信息
        System.out.println("\n=== 对象参数更新 ===");
        Student stu = new Student();
        stu.setId(2);
        stu.setName("白歌");
        stu.setAge(22);
        stu.setMajor("数据科学");
        stu.setScore(93.5);
        int rows2 = mapper.updateStudent(stu);
        System.out.println("影响行数: " + rows2);

        // 3. 更新一个不存在的 ID,验证影响行数为 0
        System.out.println("\n=== 更新不存在的学生 ===");
        int rows3 = mapper.updateMajorAndScoreById(999, "测试专业", 60.0);
        System.out.println("影响行数: " + rows3);
        if (rows3 == 0) {
            System.out.println("提示:ID=999 的学生不存在");
        }

        session.commit();
        session.close();
    }
}

实际执行结果

控制台 SQL 输出

=== 多参数更新 ===
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - ==>  Preparing: UPDATE student SET major = ?, score = ? WHERE id = ?
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - ==> Parameters: 人工智能(String), 96.0(Double), 1(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - <==    Updates: 1
影响行数: 1

=== 对象参数更新 ===
[main] DEBUG com.flying.mapper.StudentMapper.updateStudent - ==>  Preparing: UPDATE student SET name = ?, age = ?, major = ?, score = ? WHERE id = ?
[main] DEBUG com.flying.mapper.StudentMapper.updateStudent - ==> Parameters: 白歌(String), 22(Integer), 数据科学(String), 93.5(Double), 2(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.updateStudent - <==    Updates: 1
影响行数: 1

=== 更新不存在的学生 ===
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - ==>  Preparing: UPDATE student SET major = ?, score = ? WHERE id = ?
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - ==> Parameters: 测试专业(String), 60.0(Double), 999(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.updateMajorAndScoreById - <==    Updates: 0
影响行数: 0
提示:ID=999 的学生不存在

更新后数据状态

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

分析

  • @Update 的返回值直接反映数据库实际修改的行数,这是做存在性校验的天然手段
  • 多参数场景下使用 @Param 明确命名,确保 #{} 能正确绑定;传入实体对象时,#{属性名} 直接映射对象属性
  • 更新操作属于写操作,测试代码中必须调用 session.commit(),否则数据不会持久化到数据库

易错场景 / 常见误区

误区错误示例正解
更新后忘记提交事务无 session.commit()写操作必须手动提交或设置自动提交
忽略影响行数为 0 的情况不判断返回值根据业务需要,返回 0 时应提示"记录不存在"或"数据未变化"
动态更新时写死所有字段每次更新都修改全部列若只需更新部分字段,使用 <script> + <set> 或 @UpdateProvider
WHERE 条件漏写UPDATE student SET major = #{major}必须带 WHERE 条件,否则全表更新

面试考点

Q1:@Update 返回的 int 代表什么?在什么场景下会返回 0?

返回的是数据库实际受影响的数据行数。返回 0 通常有两种情况:(1) WHERE 条件匹配不到任何记录;(2) 新值与旧值完全相同,数据库认为无需更新(取决于 JDBC 驱动和数据库配置)。

Q2:注解方式下如何实现只更新传入的非空字段?

纯 @Update 字符串无法做到,需要借助 <script> 标签包裹动态 SQL(<set> + <if>),或者使用 @UpdateProvider 在 Java 代码中根据字段非空判断拼接 SQL。

Q3:@Update 和 XML 的 <update> 在事务行为上有区别吗?

没有区别。两者最终都生成 MappedStatement,由同一个 SqlSession 和 Executor 执行,事务边界由 SqlSession 控制,与注解或 XML 的声明方式无关。

Q4:MyBatis 更新时如何防止全表更新?

在业务层或拦截层对 UPDATE SQL 进行校验,确保包含 WHERE 子句;MyBatis 本身不提供强制防全表更新机制,但可通过自定义拦截器在解析 SQL 后检查是否缺少 WHERE。

小结

@Update 是注解开发中完成数据修正的核心注解。它用法简单,与 XML 的 <update> 在底层完全等价。开发者应重点关注影响行数的业务含义,以及多参数场景下的 @Param 命名规范。对于需要选择性更新字段的场景,可继续学习动态 SQL 相关注解。

下一章引子

更新之外,系统还需要支持数据删除。@Delete 注解负责替代 XML 的 <delete> 完成数据删除,同样返回影响行数。下一节将详细讲解 @Delete 的用法。

上一页
@Insert
下一页
@Delete