目录
[二、Spring 提供了哪几种事务管理方式?](#二、Spring 提供了哪几种事务管理方式?)
[1. 编程式事务](#1. 编程式事务)
[2. 声明式事务(主流方式)](#2. 声明式事务(主流方式))
[四、Spring 中事务为什么会失效?](#四、Spring 中事务为什么会失效?)
[1. 方法访问权限问题](#1. 方法访问权限问题)
[2. 方法被 final 修饰](#2. 方法被 final 修饰)
[3. 方法内部调用(最常见)](#3. 方法内部调用(最常见))
[4. Bean 未被 Spring 管理](#4. Bean 未被 Spring 管理)
[5. 多线程调用](#5. 多线程调用)
[6. 数据表不支持事务](#6. 数据表不支持事务)
[7. 传播行为配置不当](#7. 传播行为配置不当)
[8. 异常被吞掉](#8. 异常被吞掉)
[9. 抛出了不回滚的异常](#9. 抛出了不回滚的异常)
[10. 自定义了回滚规则但配置错误](#10. 自定义了回滚规则但配置错误)
[11. 嵌套事务回滚预期错误](#11. 嵌套事务回滚预期错误)
事务是后端开发中一个绕不开的话题。
在 Spring 项目中,我们几乎每天都在使用 @Transactional,但一旦事务"不生效",问题往往就开始变得棘手。
这篇文章从事务的本质认知出发,系统梳理:
- Spring 事务的两种管理方式
- 声明式事务的底层原理
- 常见事务失效场景
- 事务传播行为
- 事务隔离级别的取舍
一、对事务的理解
从本质上讲,事务是一组操作的逻辑单元,这组操作要么全部成功,要么全部失败。
数据库事务需要满足 ACID 特性:
- 原子性(Atomicity):要么全做,要么全不做
- 一致性(Consistency):事务前后数据状态一致
- 隔离性(Isolation):事务之间互不干扰
- 持久性(Durability):提交后的结果永久生效
Spring 本身并不实现事务,它只是对数据库事务进行了统一管理和封装。
二、Spring 提供了哪几种事务管理方式?
Spring 中的事务管理可以分为两大类:编程式事务 和 声明式事务
1. 编程式事务
编程式事务允许我们在代码中显式控制事务的边界,常见方式有:
- TransactionTemplate
- PlatformTransactionManager
java
transactionTemplate.execute(status -> {
// 业务逻辑
return null;
});
优点:
- 灵活
- 可以精确控制到代码块级别
缺点:
- 侵入业务代码
- 可读性差
- 不适合复杂业务
在实际项目中使用较少,更多用于特殊场景。
2. 声明式事务(主流方式)
声明式事务是 Spring 中最常用的事务管理方式:
java
@Transactional
public void save() {
// 业务逻辑
}
特点:
- 不侵入业务代码
- 只需通过注解声明事务
- Spring 自动管理事务的开启、提交和回滚
声明式事务是 Spring 事务体系的核心。
三、声明式事务的底层原理
Spring 的声明式事务并不是"魔法",而是基于 AOP + 代理机制 实现的。
整个过程可以分为两个阶段。
第一阶段:容器启动阶段
- Spring 启动时扫描 Bean
- 发现方法上存在 @Transactional
- 不会直接返回原始 Bean
- 而是为该 Bean 创建一个 代理对象
第二阶段:方法运行阶段
- 实际调用的是 代理对象的方法
- 事务拦截器在方法执行前介入
- 根据 @Transactional 配置:
- 传播行为
- 隔离级别
- 回滚规则
- 通过事务管理器开启事务
- 方法正常结束 → 提交事务
- 方法抛出异常 → 回滚事务
关键点:事务是加在代理对象上的,而不是原始对象。
四、Spring 中事务为什么会失效?
这是事务问题中最容易踩坑的一部分。
下面是常见的事务不生效场景汇总。
1. 方法访问权限问题
- 非 public 方法
- Spring 默认基于代理,无法增强
2. 方法被 final 修饰
- CGLIB 无法覆盖 final 方法
- 事务增强失败
3. 方法内部调用(最常见)
java
public void methodA() {
methodB(); // 事务失效
}
@Transactional
public void methodB() {
}
原因:内部调用绕过了代理对象
4. Bean 未被 Spring 管理
- 手动 new 出来的对象
- 容器感知不到,自然无事务
5. 多线程调用
- 事务基于线程绑定
- 新线程中事务上下文丢失
6. 数据表不支持事务
- 如 MySQL 的 MyISAM 引擎
7. 传播行为配置不当
- 事务被挂起
- 新事务被强制创建
8. 异常被吞掉
java
try {
save();
} catch (Exception e) {
// 没抛出
}
- Spring 认为方法正常结束
- 不回滚
9. 抛出了不回滚的异常
- 默认只回滚 RuntimeException
- 受检异常需显式配置
10. 自定义了回滚规则但配置错误
11. 嵌套事务回滚预期错误
- 子事务回滚不等于父事务回滚
五、事务传播行为详解
事务传播行为描述的是:一个事务方法调用另一个事务方法时,事务该如何处理
Spring 一共提供 7 种传播行为。
- REQUIRED(默认):如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到这个事务中
- SUPPORTS:如果当前有事务,则加入到这个事务;如果没有事务,则不使用事务
- REQUIRES_NEW:无论对当前是否有事务,都会新建一个事务
- MANDATORY:如果当前有事务,则加入这个事务;如果没有事务,则抛出异常
- NOT_SUPPORTED:总是非事务的执行,并挂起任何存在的事务
- NEVER:如果当前有事务,则抛出异常;如果没有事务,则以非事务方法执行
- NESTED:如果当前有事务,则在当前事务中嵌套一个事务;如果没有事务,则新建一个事务
六、事务隔离级别
事务隔离级别决定了并发事务之间的可见性。
| 隔离级别 | 能解决的问题 |
|---|---|
| READ UNCOMMITTED | 几乎不解决 |
| READ COMMITTED | 避免脏读 |
| REPEATABLE READ | 避免脏读、不可重复读 |
| SERIALIZABLE | 避免所有并发问题 |
**安全性:**Serializable > Repeatable read > Read committed > Read committed
**性能:**Serializable < Repeatable read < Read committed < Read committed
大多数情况下,使用数据库默认隔离级别即可。
七、总结
Spring 事务的核心从来不在注解,而在于:代理对象 + AOP 拦截 + 异常控制
Spring 事务,本质是基于 AOP 的方法级事务管理机制。