欢迎光临
我们一直在努力

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

目录

前言

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

1.1 事务的定义

1.2 为什么需要事务?

1.3 事务的核心操作

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

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

核心依赖与组件

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

优缺点

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

实现步骤

核心原理

注意事项

异常捕获后的回滚方案

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

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

默认行为

配置 rollbackFor

扩展用法

3.2 isolation:事务隔离级别

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

3.2.2 Spring 的五种隔离级别

配置方式

选择建议

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

传播机制的 7 种行为

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

准备工作

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

代码配置

执行结果

流程分析

4.2 REQUIRES_NEW:新建独立事务

代码配置

执行结果

流程分析

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

代码配置

执行结果(未捕获异常)

流程分析

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

执行结果

核心原理:保存点(Savepoint)

4.4 NESTED vs REQUIRED:关键区别

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

5.1 @Transactional 注解不生效的场景

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 事务本质也是对这三步的封装:

  • 开启事务:start transaction / begin(操作执行前开启);
  • 提交事务:commit(所有操作无异常时提交,数据永久生效);
  • 回滚事务:rollback(任意操作异常时回滚,恢复到操作前状态)。
  • 二、Spring 事务的两种实现方式

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

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

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

    核心依赖与组件

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

    • DataSourceTransactionManager:负责事务的开启、提交、回滚;
    • TransactionDefinition:定义事务属性(如隔离级别、传播机制);
    • TransactionStatus:事务的当前状态(如是否活跃、是否需要回滚)。
    代码实现(以用户注册为例)
  • 准备工作:创建数据库表(用户表user_info、日志表log_info)、实体类、Mapper 接口(文档中已提供,此处省略);
  • Controller 层手动控制事务:
  • 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 事务的推荐用法。

    实现步骤
  • 引入依赖(SpringBoot 项目已内置spring-tx,无需手动引入):
  • <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    </dependency>

  • 在需要事务的方法 / 类上添加@Transactional注解:
  • 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) 使用该注解(业务层通常包含多个数据操作,便于控制事务边界)。
    异常捕获后的回滚方案

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

  • 重新抛出异常:
  • @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 "注册成功";
    }

  • 手动触发回滚:
  • 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 回滚,对非运行时异常(如IOException、SQLException)不回滚。

    示例验证:

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

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

    配置 rollbackFor

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

    // 对所有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(默认值),即沿用数据库的隔离级别:

  • Isolation.DEFAULT:默认值,使用数据库的隔离级别(MySQL 为 REPEATABLE READ);
  • Isolation.READ_UNCOMMITTED:对应 MySQL 的读未提交;
  • Isolation.READ_COMMITTED:对应 MySQL 的读已提交;
  • Isolation.REPEATABLE_READ:对应 MySQL 的可重复读;
  • Isolation.SERIALIZABLE:对应 MySQL 的串行化。
  • 配置方式

    // 设置隔离级别为读已提交
    @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(默认):加入当前事务

    代码配置

    // 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 "操作成功";
    }
    }

    执行结果

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

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

    代码配置

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

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

    执行结果

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

    流程分析
  • Controller 的p1方法开启事务;
  • userService.registryUser新建独立事务,插入用户数据后提交事务(不受后续异常影响);
  • logService.insertLog新建独立事务,抛出异常,该事务回滚(日志数据未插入);
  • 两个事务相互独立,日志事务的异常不影响用户事务。
  • 4.3 NESTED:嵌套事务(支持局部回滚)

    代码配置

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

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

    执行结果(未捕获异常)

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

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

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

    @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:关键区别

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

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

    5.1 @Transactional 注解不生效的场景

  • 修饰非 public 方法(如private、protected);
  • 异常被try-catch捕获且未重新抛出;
  • 数据源未配置事务管理器(SpringBoot 自动配置,手动配置时需注意);
  • 同一个类中无事务方法调用有事务方法(AOP 无法拦截内部调用); @Service
    public class UserService {
    // 无事务方法
    public void test() {
    registryUser("admin", "123456"); // 内部调用,@Transactional不生效
    }

    @Transactional
    public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
    }
    }

  • 事务管理器配置错误(如多数据源时未指定对应事务管理器)。
  • 5.2 性能优化建议

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

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

  • 事务的本质是 “原子性操作集合”,解决数据一致性问题;
  • Spring 事务有两种实现方式:编程式(灵活但繁琐)和声明式(@Transactional注解,推荐);
  • @Transactional的三大核心属性:rollbackFor(控制回滚异常)、isolation(控制并发问题)、propagation(控制事务传播);
  • 事务传播机制是重点,REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(局部回滚)是高频使用场景;
  • 嵌套事务通过保存点机制实现局部回滚,适用于 “部分操作可独立失败” 的场景。
  • 实际开发中,建议优先使用声明式事务,根据业务场景灵活配置隔离级别和传播机制:

    • 转账、支付等核心场景:用REQUIRED隔离级别,确保操作原子性;
    • 注册 + 日志、下单 + 库存等场景:用NESTED或REQUIRES_NEW,实现部分操作独立回滚;
    • 非核心查询场景:用READ_COMMITTED隔离级别,提升并发性能。
    赞(0)
    未经允许不得转载:171主机测评 » Spring 事务深度解析:实现方式、隔离级别与传播机制全攻略
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址