Spring 事务深度解析:实现方式、隔离级别与传播机制全攻略

目录

前言

一、事务基础回顾:是什么?为什么需要?

[1.1 事务的定义](#1.1 事务的定义)

[1.2 为什么需要事务?](#1.2 为什么需要事务?)

[1.3 事务的核心操作](#1.3 事务的核心操作)

[二、Spring 事务的两种实现方式](#二、Spring 事务的两种实现方式)

[2.1 编程式事务:手动控制事务生命周期](#2.1 编程式事务:手动控制事务生命周期)

核心依赖与组件

代码实现(以用户注册为例)

优缺点

[2.2 声明式事务:@Transactional 注解一键搞定](#2.2 声明式事务:@Transactional 注解一键搞定)

实现步骤

核心原理

注意事项

异常捕获后的回滚方案

[三、@Transactional 注解详解:三大核心属性](#三、@Transactional 注解详解:三大核心属性)

[3.1 rollbackFor:指定回滚的异常类型](#3.1 rollbackFor:指定回滚的异常类型)

默认行为

[配置 rollbackFor](#配置 rollbackFor)

扩展用法

[3.2 isolation:事务隔离级别](#3.2 isolation:事务隔离级别)

[3.2.1 MySQL 的四种隔离级别(SQL 标准)](#3.2.1 MySQL 的四种隔离级别(SQL 标准))

[3.2.2 Spring 的五种隔离级别](#3.2.2 Spring 的五种隔离级别)

配置方式

选择建议

[3.3 propagation:事务传播机制(核心重点)](#3.3 propagation:事务传播机制(核心重点))

[传播机制的 7 种行为](#传播机制的 7 种行为)

四、事务传播机制场景演示:实战理解核心行为

准备工作

[4.1 REQUIRED(默认):加入当前事务](#4.1 REQUIRED(默认):加入当前事务)

代码配置

执行结果

流程分析

[4.2 REQUIRES_NEW:新建独立事务](#4.2 REQUIRES_NEW:新建独立事务)

代码配置

执行结果

流程分析

[4.3 NESTED:嵌套事务(支持局部回滚)](#4.3 NESTED:嵌套事务(支持局部回滚))

代码配置

执行结果(未捕获异常)

流程分析

局部回滚场景(捕获异常)

执行结果

核心原理:保存点(Savepoint)

[4.4 NESTED vs REQUIRED:关键区别](#4.4 NESTED vs REQUIRED:关键区别)

[五、Spring 事务常见问题与避坑指南](#五、Spring 事务常见问题与避坑指南)

[5.1 @Transactional 注解不生效的场景](#5.1 @Transactional 注解不生效的场景)

[5.2 性能优化建议](#5.2 性能优化建议)

六、总结


前言

在后端开发中,数据一致性是核心诉求之一。无论是转账时的金额流转,还是秒杀场景的库存扣减,稍有不慎就会导致数据错乱 ------ 比如 A 账户扣款成功但 B 账户未到账,下单成功但库存未减少。而事务,正是解决这类问题的关键技术。Spring 框架对事务进行了高度封装,提供了灵活易用的事务管理能力。本文将从事务基础出发,深入拆解 Spring 事务的实现方式、核心注解配置、隔离级别,并重点剖析事务传播机制的 7 种行为与实际应用场景,帮你彻底掌握 Spring 事务的核心用法。

一、事务基础回顾:是什么?为什么需要?

在学习 Spring 事务之前,我们先回顾数据库事务的核心概念,这是理解 Spring 事务的基础。

1.1 事务的定义

事务是一组不可分割的数据库操作集合,这组操作要么全部成功执行并提交,要么全部失败并回滚,不存在 "部分成功" 的中间状态。就像快递发货,下单、扣库存、生成物流单这一系列操作,必须同时完成才算交易成功,任何一步失败都要回到初始状态。

1.2 为什么需要事务?

事务的核心价值是保证数据一致性,我们通过两个经典场景理解:

  • 转账场景:A 账户转出 100 元,B 账户转入 100 元。如果没有事务,A 账户扣款成功后,B 账户转入操作失败,会导致 100 元 "凭空消失";
  • 秒杀场景:用户下单成功后,需要扣减对应商品库存。如果下单成功但库存扣减失败,会导致超卖(实际库存为 0 但仍有订单生成)。

事务通过 "原子性" 特性,确保这一系列操作要么全成,要么全败,从根本上避免数据不一致。

1.3 事务的核心操作

数据库层面,事务的操作有三步,Spring 事务本质也是对这三步的封装:

  1. 开启事务:start transaction / begin(操作执行前开启);
  2. 提交事务:commit(所有操作无异常时提交,数据永久生效);
  3. 回滚事务:rollback(任意操作异常时回滚,恢复到操作前状态)。

二、Spring 事务的两种实现方式

Spring 支持两种事务管理方式:编程式事务(手动控制)和声明式事务(注解自动控制)。实际开发中,声明式事务因简洁高效成为主流,编程式事务仅用于特殊场景。

2.1 编程式事务:手动控制事务生命周期

编程式事务需要开发者手动编写代码开启、提交、回滚事务,灵活性高但代码繁琐。

核心依赖与组件

SpringBoot 内置了DataSourceTransactionManager(事务管理器),无需额外引入依赖,核心组件:

  • DataSourceTransactionManager:负责事务的开启、提交、回滚;
  • TransactionDefinition:定义事务属性(如隔离级别、传播机制);
  • TransactionStatus:事务的当前状态(如是否活跃、是否需要回滚)。
代码实现(以用户注册为例)
  1. 准备工作:创建数据库表(用户表user_info、日志表log_info)、实体类、Mapper 接口(文档中已提供,此处省略);
  2. Controller 层手动控制事务:
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    // 注入事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 注入事务属性定义
    @Autowired
    private TransactionDefinition transactionDefinition;
    // 注入业务层
    @Autowired
    private UserService userService;

    @RequestMapping("/registry")
    public String registry(String name, String password) {
        // 1. 开启事务
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
        try {
            // 2. 执行核心业务(用户注册)
            userService.registryUser(name, password);
            // 3. 无异常,提交事务
            transactionManager.commit(status);
            return "注册成功";
        } catch (Exception e) {
            // 4. 有异常,回滚事务
            transactionManager.rollback(status);
            return "注册失败";
        }
    }
}
优缺点
  • 优点:完全手动控制事务边界,灵活处理复杂场景;
  • 缺点:代码冗余,事务逻辑与业务逻辑耦合,不利于维护。

2.2 声明式事务:@Transactional 注解一键搞定

声明式事务基于 AOP 实现,通过@Transactional注解自动完成事务的开启、提交、回滚,无需编写额外事务代码,是 Spring 事务的推荐用法。

实现步骤
  1. 引入依赖(SpringBoot 项目已内置spring-tx,无需手动引入):
java 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
  1. 在需要事务的方法 / 类上添加@Transactional注解:
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;

    // 添加入注解,该方法自动支持事务
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        // 执行核心业务
        userService.registryUser(name, password);
        // 模拟异常(测试回滚)
        int a = 10 / 0;
        return "注册成功";
    }
}
核心原理
  • 当方法被@Transactional修饰时,Spring 会通过 AOP 动态生成代理对象;
  • 方法执行前,代理对象自动开启事务;
  • 方法执行无异常时,自动提交事务;
  • 方法抛出未捕获的异常时,自动回滚事务。
注意事项
  • @Transactional仅对public方法生效(修饰非 public 方法时不报错但无事务效果);
  • 若异常被try-catch捕获且未重新抛出,事务不会回滚(Spring 无法感知异常);
  • 建议在业务层(Service) 使用该注解(业务层通常包含多个数据操作,便于控制事务边界)。
异常捕获后的回滚方案

如果需要捕获异常且让事务回滚,有两种方式:

  1. 重新抛出异常:
java 复制代码
@Transactional
public String registry(String name, String password) {
    try {
        userService.registryUser(name, password);
        int a = 10 / 0;
    } catch (Exception e) {
        e.printStackTrace();
        // 重新抛出异常,触发回滚
        throw e;
    }
    return "注册成功";
}
  1. 手动触发回滚:
java 复制代码
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Transactional
public String registry(String name, String password) {
    try {
        userService.registryUser(name, password);
        int a = 10 / 0;
    } catch (Exception e) {
        e.printStackTrace();
        // 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "注册成功";
}

三、@Transactional 注解详解:三大核心属性

@Transactional注解提供了多个属性,用于灵活配置事务行为,核心属性有 3 个:rollbackFor(异常回滚规则)、isolation(隔离级别)、propagation(传播机制)。

3.1 rollbackFor:指定回滚的异常类型

默认行为

Spring 事务默认仅对运行时异常(RuntimeException)和 Error 回滚,对非运行时异常(如IOExceptionSQLException)不回滚。

示例验证:

java 复制代码
@Transactional
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
    userService.registryUser(name, password);
    // 抛出非运行时异常(IOException)
    throw new IOException();
}

运行结果:事务未回滚,用户数据成功插入数据库。

配置 rollbackFor

若需要对所有异常都回滚,或指定特定异常回滚,通过rollbackFor配置:

java 复制代码
// 对所有Exception子类都回滚
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
    userService.registryUser(name, password);
    throw new IOException();
}

运行结果:事务回滚,用户数据未插入。

扩展用法
  • 指定多个异常类型:@Transactional(rollbackFor = {IOException.class, SQLException.class})
  • 反向配置(不回滚的异常):noRollbackFor = XXXException.class(慎用,可能导致数据不一致)。

3.2 isolation:事务隔离级别

事务隔离级别解决的是 "多个事务同时操作同一批数据时的并发问题",主要有三类并发问题:

  • 脏读:一个事务读取到另一个事务未提交的数据(可能回滚,导致读取的数据无效);
  • 不可重复读:同一事务内多次查询同一数据,结果不一致(其他事务修改并提交了该数据);
  • 幻读:同一事务内多次执行同一查询,返回的结果集行数不一致(其他事务新增 / 删除了数据)。
3.2.1 MySQL 的四种隔离级别(SQL 标准)

MySQL 支持 SQL 标准定义的四种隔离级别,默认隔离级别为可重复读(REPEATABLE READ)

隔离级别 脏读 不可重复读 幻读 说明
读未提交(READ UNCOMMITTED) 最低级别,允许读取未提交数据,性能最高但一致性最差
读已提交(READ COMMITTED) 只能读取已提交数据,避免脏读,Oracle 默认级别
可重复读(REPEATABLE READ) 同一事务内多次查询结果一致,避免脏读和不可重复读,MySQL 默认级别
串行化(SERIALIZABLE) 最高级别,事务串行执行,完全避免并发问题,但性能最差
3.2.2 Spring 的五种隔离级别

Spring 在 MySQL 隔离级别的基础上,增加了DEFAULT(默认值),即沿用数据库的隔离级别:

  1. Isolation.DEFAULT:默认值,使用数据库的隔离级别(MySQL 为 REPEATABLE READ);
  2. Isolation.READ_UNCOMMITTED:对应 MySQL 的读未提交;
  3. Isolation.READ_COMMITTED:对应 MySQL 的读已提交;
  4. Isolation.REPEATABLE_READ:对应 MySQL 的可重复读;
  5. Isolation.SERIALIZABLE:对应 MySQL 的串行化。
配置方式
java 复制代码
// 设置隔离级别为读已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}
选择建议
  • 绝大多数场景:使用默认隔离级别(REPEATABLE READ),兼顾一致性和性能;
  • 高一致性要求(如金融场景):使用SERIALIZABLE,但需注意性能损耗;
  • 低一致性要求(如日志统计):可使用READ_COMMITTED,提升并发性能。

3.3 propagation:事务传播机制(核心重点)

多个被@Transactional修饰的方法相互调用时,事务如何在方法间传递,这就是传播机制。比如:方法 A(有事务)调用方法 B(有事务),B 是加入 A 的事务,还是新建独立事务?

传播机制的 7 种行为

Spring 定义了 7 种传播行为,通过@Transactional(propagation = 传播行为)配置,核心常用的是前 3 种:

传播行为 中文说明 核心逻辑 通俗比喻(结婚买房)
REQUIRED(默认) 必须有事务 若当前存在事务,加入该事务;若不存在,新建事务 结婚必须有房:你有房就一起住,没房就一起买
REQUIRES_NEW 新建独立事务 无论当前是否有事务,都新建独立事务,挂起当前事务 必须买新房:不管你有没有房,都要重新买一套,各自独立
NESTED 嵌套事务 若当前有事务,创建嵌套事务(依赖保存点);若不存在,新建事务 以房为基础:你有房就用你的房,在房里搞 "小项目";没房就一起买
SUPPORTS 支持事务 若当前有事务,加入;若没有,以非事务方式运行 可有可无:你有房就一起住,没房就租房
MANDATORY 强制事务 若当前有事务,加入;若没有,抛出异常 必须有房才结婚:没房就不结
NOT_SUPPORTED 不支持事务 以非事务方式运行,若当前有事务,挂起事务 不需要房:不管你有没有房,我都租房住
NEVER 禁止事务 以非事务方式运行,若当前有事务,抛出异常 不能有房:你有房就不结婚

四、事务传播机制场景演示:实战理解核心行为

我们通过 "用户注册 + 记录操作日志" 的场景,演示 3 种核心传播行为的差异(用户注册和记录日志都是带事务的方法)。

准备工作

  • 业务逻辑:用户注册(UserService.registryUser)后,记录操作日志(LogService.insertLog);
  • 模拟异常:在日志记录方法中抛出10/0的运行时异常。

4.1 REQUIRED(默认):加入当前事务

代码配置
java 复制代码
// UserService
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password); // 插入用户
    }
}

// LogService
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name, String op) {
        int a = 10 / 0; // 模拟异常
        logInfoMapper.insertLog(name, op); // 插入日志
    }
}

// Controller
@RestController
@RequestMapping("/propaga")
public class PropagationController {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/p1")
    public String p1(String name, String password) {
        userService.registryUser(name, password); // 调用用户注册
        logService.insertLog(name, "用户注册"); // 调用日志记录
        return "操作成功";
    }
}
执行结果

数据库中无用户数据和日志数据插入

流程分析
  1. Controller 的p1方法开启事务;
  2. userService.registryUser加入p1的事务,插入用户数据成功;
  3. logService.insertLog加入p1的事务,抛出异常;
  4. 由于所有操作在同一个事务中,异常触发整体回滚,用户数据和日志数据均回滚。

4.2 REQUIRES_NEW:新建独立事务

代码配置

将两个 Service 方法的传播机制改为REQUIRES_NEW

java 复制代码
// UserService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}

// LogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
    int a = 10 / 0;
    logInfoMapper.insertLog(name, op);
}
执行结果

数据库中用户数据插入成功,日志数据未插入

流程分析
  1. Controller 的p1方法开启事务;
  2. userService.registryUser新建独立事务,插入用户数据后提交事务(不受后续异常影响);
  3. logService.insertLog新建独立事务,抛出异常,该事务回滚(日志数据未插入);
  4. 两个事务相互独立,日志事务的异常不影响用户事务。

4.3 NESTED:嵌套事务(支持局部回滚)

代码配置

将两个 Service 方法的传播机制改为NESTED

java 复制代码
// UserService
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}

// LogService
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
    int a = 10 / 0;
    logInfoMapper.insertLog(name, op);
}
执行结果(未捕获异常)

数据库中无用户数据和日志数据插入

流程分析
  1. Controller 的p1方法开启事务(父事务);
  2. userService.registryUser创建嵌套事务(子事务 1),插入用户数据;
  3. logService.insertLog创建嵌套事务(子事务 2),抛出异常,子事务 2 回滚;
  4. 由于子事务 2 未捕获异常,异常向上传播,父事务回滚,子事务 1 也随之回滚。
局部回滚场景(捕获异常)

修改LogService,捕获异常并手动回滚当前嵌套事务:

java 复制代码
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name, String op) {
        try {
            int a = 10 / 0;
            logInfoMapper.insertLog(name, op);
        } catch (Exception e) {
            e.printStackTrace();
            // 手动回滚当前嵌套事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}
执行结果

数据库中用户数据插入成功,日志数据未插入

核心原理:保存点(Savepoint)

嵌套事务的局部回滚依赖数据库的保存点(Savepoint) 机制:

  • 父事务开启后,每个嵌套事务执行前会创建一个保存点;
  • 嵌套事务回滚时,仅回滚到当前保存点,不影响父事务和其他嵌套事务的已执行操作;
  • 父事务回滚时,会回滚所有嵌套事务(包括已提交的嵌套事务)。

4.4 NESTED vs REQUIRED:关键区别

对比维度 REQUIRED NESTED
事务关系 同一事务 父 - 子嵌套事务(保存点隔离)
回滚范围 要么全回滚,要么全提交 支持局部回滚(子事务回滚不影响父事务)
异常传播 子事务异常直接导致整体回滚 子事务异常可捕获,仅回滚当前子事务
适用场景 多个操作必须同时成功 / 失败(如转账) 多个操作可独立回滚(如注册 + 送积分,积分失败不影响注册)

五、Spring 事务常见问题与避坑指南

5.1 @Transactional 注解不生效的场景

  1. 修饰非 public 方法(如privateprotected);

  2. 异常被try-catch捕获且未重新抛出;

  3. 数据源未配置事务管理器(SpringBoot 自动配置,手动配置时需注意);

  4. 同一个类中无事务方法调用有事务方法(AOP 无法拦截内部调用);

    java 复制代码
    @Service
    public class UserService {
        // 无事务方法
        public void test() {
            registryUser("admin", "123456"); // 内部调用,@Transactional不生效
        }
    
        @Transactional
        public void registryUser(String name, String password) {
            userInfoMapper.insert(name, password);
        }
    }
  5. 事务管理器配置错误(如多数据源时未指定对应事务管理器)。

5.2 性能优化建议

  1. 避免大事务:事务范围越小越好,不要在事务中执行非数据库操作(如调用第三方接口、文件 IO);
  2. 合理选择隔离级别:非核心场景避免使用SERIALIZABLE,减少锁竞争;
  3. 传播机制按需选择:无需独立事务时用REQUIRED,需独立事务时用REQUIRES_NEW,避免过度使用REQUIRES_NEW导致事务过多;
  4. 避免长事务:长事务会占用数据库连接,导致连接池耗尽,影响系统并发能力。

六、总结

Spring 事务是保证数据一致性的核心技术,本文从基础到实战,全面解析了 Spring 事务的核心知识点:

  1. 事务的本质是 "原子性操作集合",解决数据一致性问题;
  2. Spring 事务有两种实现方式:编程式(灵活但繁琐)和声明式(@Transactional注解,推荐);
  3. @Transactional的三大核心属性:rollbackFor(控制回滚异常)、isolation(控制并发问题)、propagation(控制事务传播);
  4. 事务传播机制是重点,REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(局部回滚)是高频使用场景;
  5. 嵌套事务通过保存点机制实现局部回滚,适用于 "部分操作可独立失败" 的场景。

实际开发中,建议优先使用声明式事务,根据业务场景灵活配置隔离级别和传播机制:

  • 转账、支付等核心场景:用REQUIRED隔离级别,确保操作原子性;
  • 注册 + 日志、下单 + 库存等场景:用NESTEDREQUIRES_NEW,实现部分操作独立回滚;
  • 非核心查询场景:用READ_COMMITTED隔离级别,提升并发性能。
相关推荐
小园子的小菜2 小时前
深入剖析HBase HFile原理:文件结构、Block协作与缓存机制
数据库·缓存·hbase
看得见的风2 小时前
Claude Code + CCR配置(含OpenRouter、GLM、Kimi Coding Plan)
开发语言
L_09072 小时前
【Linux】进程状态
linux·开发语言·c++
roman_日积跬步-终至千里2 小时前
【Java并发】用 JMM 与 Happens-Before 解决多线程可见性与有序性问题
java·开发语言·spring
空空kkk2 小时前
SSM项目练习——hami音乐(三)
java·数据库
2401_838472512 小时前
C++异常处理最佳实践
开发语言·c++·算法
m0_736919102 小时前
C++中的类型标签分发
开发语言·c++·算法
好奇的菜鸟2 小时前
Ubuntu 18.04 启用root账户图形界面登录指南
数据库·ubuntu·postgresql
天桥下的卖艺者2 小时前
使用R语言编写一个生成金字塔图形的函数
开发语言·数据库·r语言