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

    • 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

导学

本节将掌握MyBatis中update映射元素的配置与使用。你将学会如何编写更新语句,理解update的返回值含义,掌握更新指定字段的写法,并了解动态更新的基础概念,为第04章的动态SQL做铺垫。

定义

update是SQL映射文件中用于定义修改语句的元素。在JDBC原始写法中,更新数据需要手动拼接UPDATE语句、设置WHERE条件参数、执行executeUpdate()并获取影响行数。update元素将这一流程声明化:开发者只需在XML中写好SQL模板,MyBatis自动完成参数绑定、预编译执行,并将JDBC返回的影响行数直接传递给调用方。

适用位置与核心属性

update元素书写在映射文件的<mapper>根标签内部,与select、insert、delete并列。

属性是否必填说明
id是映射语句的唯一标识,与Mapper接口方法名对应
parameterType否传入参数的全限定类名或别名,MyBatis可自动推断
flushCache否执行后是否清空本地缓存与二级缓存,默认true
timeout否驱动等待数据库返回的最大秒数,默认未设置

update的属性相对精简,因为更新语句通常只需要SQL模板和参数即可。flushCache默认true,意味着每次更新都会清空相关缓存,保证后续查询读到最新数据。

核心原理

MyBatis执行update语句时,通过PreparedStatement完成预编译更新,并将数据库返回的影响行数透传给Mapper接口的调用方。

  1. Java调用:开发者调用StudentMapper.updateStudent(student)。
  2. SqlSession & Executor:会话层调度执行器,准备执行更新。
  3. StatementHandler:创建PreparedStatement,绑定#{name}、#{age}、#{id}等参数。
  4. JDBC执行:向MySQL 5.7发送UPDATE语句,数据库完成数据修改。
  5. 返回影响行数:MySQL返回实际被修改的行数(注意:若新值与旧值相同,MySQL可能返回0)。
  6. 清空缓存:flushCache默认true,本次更新会清空当前SqlSession的一级缓存,保证后续查询不读到脏数据。

完整示例

场景说明

乐途公司技术部的学员信息发生变更:小崔从"计算机科学"专业转入"人工智能"专业,同时年龄增长一岁。管理员需要通过学生管理系统更新这两条信息,并确认更新是否成功。

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

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

完整的映射文件片段与Java代码

POJO类

package com.flywing.entity;

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

    // Getter与Setter省略
    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.flywing.mapper;

import com.flywing.entity.Student;

public interface StudentMapper {
    // 更新学员信息,返回影响行数
    int updateStudent(Student student);

    // 按ID更新分数
    int updateScoreById(Integer id, Double newScore);
}

映射文件 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.flywing.mapper.StudentMapper">

    <!-- 场景一:根据对象更新多个字段 -->
    <update id="updateStudent" parameterType="com.flywing.entity.Student">
        UPDATE student
        SET name   = #{name},
            age    = #{age},
            major  = #{major},
            score  = #{score}
        WHERE id = #{id}
    </update>

    <!-- 场景二:按ID更新单个字段(多参数用@param或Map,此处演示Map方式) -->
    <update id="updateScoreById">
        UPDATE student
        SET score = #{newScore}
        WHERE id = #{id}
    </update>

</mapper>

测试代码

package com.flywing.test;

import com.flywing.entity.Student;
import com.flywing.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;
import java.util.HashMap;
import java.util.Map;

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);

        // 场景一:更新小崔的专业和年龄
        Student cui = new Student();
        cui.setId(3);          // 小崔的ID
        cui.setName("小崔");    // 姓名不变也需传入
        cui.setAge(21);        // 年龄+1
        cui.setMajor("人工智能"); // 专业变更
        cui.setScore(92.0);    // 分数不变也需传入

        int rows1 = mapper.updateStudent(cui);
        System.out.println("场景一影响行数:" + rows1);

        // 场景二:将白歌的分数更新为90.0
        Map<String, Object> params = new HashMap<>();
        params.put("id", 2);
        params.put("newScore", 90.0);
        int rows2 = mapper.updateScoreById(params);
        System.out.println("场景二影响行数:" + rows2);

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

实际执行结果

控制台SQL输出

[DEBUG] com.flywing.mapper.StudentMapper.updateStudent - ==>  Preparing: UPDATE student SET name = ?, age = ?, major = ?, score = ? WHERE id = ?
[DEBUG] com.flywing.mapper.StudentMapper.updateStudent - ==> Parameters: 小崔(String), 21(Integer), 人工智能(String), 92.0(Double), 3(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.updateStudent - <==    Updates: 1
场景一影响行数:1

[DEBUG] com.flywing.mapper.StudentMapper.updateScoreById - ==>  Preparing: UPDATE student SET score = ? WHERE id = ?
[DEBUG] com.flywing.mapper.StudentMapper.updateScoreById - ==> Parameters: 90.0(Double), 2(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.updateScoreById - <==    Updates: 1
场景二影响行数:1

更新后的数据库状态

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

分析

  1. 全字段更新的隐患:场景一的updateStudent要求调用方传入所有字段,即使某些字段未变更也要原样传回,否则会被覆盖为null。这在实际开发中非常不便,也是动态更新(第04章<set>标签)要解决的核心痛点。
  2. 影响行数的含义:返回值表示实际被修改的行数。若WHERE id = 999(不存在的记录),返回值将为0,开发者可据此判断更新是否命中目标。
  3. 多参数传递:场景二演示了通过Map传递多个参数的方式。另一种常见方式是在Mapper接口中使用@Param注解,如updateScoreById(@Param("id") Integer id, @Param("newScore") Double newScore)。

易错场景/常见误区

误区正解
更新时只传变更字段,其他字段不写基本update会覆盖未传字段为null;应传入完整对象,或等待第04章学习<set>动态更新
忘记写WHERE条件缺少WHERE会导致全表更新,是极其危险的操作;务必确认条件存在且能命中目标行
认为返回值大于0就一定更新了数据若新值与旧值完全相同,MySQL可能返回0(取决于jdbcURL中useAffectedRows配置)
更新后不commit与insert相同,update也需要session.commit()才能持久化
用update返回的影响行数作为业务成功唯一标准应结合SELECT再次查询确认数据状态,特别是在并发场景下

面试考点

Q1:MyBatis的update返回什么?

返回int,表示实际被数据库修改的行数。若WHERE条件未命中任何记录,返回0。注意该值来自JDBC的Statement.getUpdateCount(),具体数值受MySQL连接参数useAffectedRows影响。

Q2:如果只想更新部分字段,如何避免将其他字段覆盖为null?

基本update元素无法做到,必须传入完整对象。要实现只更新非空字段,需要使用动态SQL的<set>标签配合<if>判断,这将在第04章"动态SQL"中详细讲解。

Q3:flushCache在update中默认是什么?为什么?

默认true。因为更新操作会改变数据库状态,如果不清空缓存,后续查询可能读到旧数据(脏读)。清空缓存保证了数据一致性,但频繁更新会导致缓存命中率下降,这是需要权衡的设计点。

Q4:MyBatis更新时parameterType可以省略吗?

可以。MyBatis会根据传入的参数自动推断类型。对于单参数(如POJO、基本类型、Map),通常无需显式声明parameterType。但显式书写有助于提高XML的可读性和维护性。

小结

update元素负责数据修改的声明化映射。它的核心属性精简,但使用时需特别注意两点:一是WHERE条件必须精确,防止误更新全表;二是基本update要求传入完整对象,否则会覆盖未传字段。返回值int表示实际影响行数,可用于判断更新是否命中。动态更新(部分字段更新)的痛点将在第04章通过<set>标签彻底解决。

下一章引子

数据有增有改,自然也有删。当学员退学、信息过期或需要清理测试数据时,delete元素负责将记录从数据库中移除。与update类似,delete也需要精确的WHERE条件,并且支持批量删除的高效写法。下一节将讲解delete的基本用法、按ID删除与批量删除的示例,以及为第04章foreach做铺垫的集合参数传递。

上一页
insert
下一页
delete