目录
[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:嵌套事务(支持局部回滚))
[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 事务本质也是对这三步的封装:
- 开启事务:
start transaction / begin(操作执行前开启); - 提交事务:
commit(所有操作无异常时提交,数据永久生效); - 回滚事务:
rollback(任意操作异常时回滚,恢复到操作前状态)。
二、Spring 事务的两种实现方式
Spring 支持两种事务管理方式:编程式事务(手动控制)和声明式事务(注解自动控制)。实际开发中,声明式事务因简洁高效成为主流,编程式事务仅用于特殊场景。
2.1 编程式事务:手动控制事务生命周期
编程式事务需要开发者手动编写代码开启、提交、回滚事务,灵活性高但代码繁琐。
核心依赖与组件
SpringBoot 内置了
DataSourceTransactionManager(事务管理器),无需额外引入依赖,核心组件:
DataSourceTransactionManager:负责事务的开启、提交、回滚;TransactionDefinition:定义事务属性(如隔离级别、传播机制);TransactionStatus:事务的当前状态(如是否活跃、是否需要回滚)。
代码实现(以用户注册为例)
- 准备工作:创建数据库表(用户表
user_info、日志表log_info)、实体类、Mapper 接口(文档中已提供,此处省略); - 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 事务的推荐用法。
实现步骤
- 引入依赖(SpringBoot 项目已内置
spring-tx,无需手动引入):
java
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
- 在需要事务的方法 / 类上添加
@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) 使用该注解(业务层通常包含多个数据操作,便于控制事务边界)。
异常捕获后的回滚方案
如果需要捕获异常且让事务回滚,有两种方式:
- 重新抛出异常:
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 "注册成功";
}
- 手动触发回滚:
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 回滚,对非运行时异常(如IOException、SQLException)不回滚。
示例验证:
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(默认值),即沿用数据库的隔离级别:
Isolation.DEFAULT:默认值,使用数据库的隔离级别(MySQL 为 REPEATABLE READ);Isolation.READ_UNCOMMITTED:对应 MySQL 的读未提交;Isolation.READ_COMMITTED:对应 MySQL 的读已提交;Isolation.REPEATABLE_READ:对应 MySQL 的可重复读;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 "操作成功";
}
}
执行结果
数据库中无用户数据和日志数据插入。
流程分析
- Controller 的
p1方法开启事务; userService.registryUser加入p1的事务,插入用户数据成功;logService.insertLog加入p1的事务,抛出异常;- 由于所有操作在同一个事务中,异常触发整体回滚,用户数据和日志数据均回滚。
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);
}
执行结果
数据库中用户数据插入成功,日志数据未插入。
流程分析
- Controller 的
p1方法开启事务; userService.registryUser新建独立事务,插入用户数据后提交事务(不受后续异常影响);logService.insertLog新建独立事务,抛出异常,该事务回滚(日志数据未插入);- 两个事务相互独立,日志事务的异常不影响用户事务。
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);
}
执行结果(未捕获异常)
数据库中无用户数据和日志数据插入。
流程分析
- Controller 的
p1方法开启事务(父事务); userService.registryUser创建嵌套事务(子事务 1),插入用户数据;logService.insertLog创建嵌套事务(子事务 2),抛出异常,子事务 2 回滚;- 由于子事务 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 注解不生效的场景
-
修饰非 public 方法(如
private、protected); -
异常被
try-catch捕获且未重新抛出; -
数据源未配置事务管理器(SpringBoot 自动配置,手动配置时需注意);
-
同一个类中无事务方法调用有事务方法(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.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隔离级别,提升并发性能。