前提:
Spring 事务底层依靠 AOP 动态代理,只有代理对象调用方法才会触发事务增强;
同时事务生效需要满足数据库引擎支持事务(InnoDB,MyISAM 不支持)。
注解:
@Transactional
1. 方法不是 public(最常见)
Spring AOP 只拦截 public 方法,private/protected/default 方法加注解完全无效。
java
@Service
public class UserService {
// ❌ private,事务失效
@Transactional
private void addUser() {
userMapper.insert(...);
int i = 1/0; // 抛异常不会回滚
}
}
2. 同类内部方法调用(this 调用,不走代理)
在同一个类中,直接 this.xxx() 调用带 @Transactional 的方法,不是代理对象调用,AOP 不会切入。
java
@Service
public class UserService {
public void test() {
// ❌ this调用,不是代理对象,addUser事务失效
this.addUser();
}
@Transactional
public void addUser() {
userMapper.insert(...);
int i = 1/0; // 异常不回滚
}
}
✅ 解决方案:
自身注入 UserService self;,self.addUser()。
3. 异常类型不对:捕获了异常 / 抛出非回滚异常
@Transactional 默认只对 RuntimeException 和 Error 触发回滚;
受检异常(Exception)默认不回滚。
场景 3.1 自己 try-catch 吃掉异常
java
@Transactional
public void addUser() {
try {
userMapper.insert(...);
int i = 1/0;
} catch (Exception e) {
// ❌ 异常被捕获,没有抛出给AOP,事务不会回滚
log.error("error", e);
}
}
场景 3.2 抛出受检异常,没指定 rollbackFor
java
@Transactional
public void addUser() throws IOException {
userMapper.insert(...);
throw new IOException("IO异常");
// ❌ IOException是受检异常,默认不回滚
}
✅ 修复:@Transactional(rollbackFor = Exception.class)
4. 传播行为配置错误
典型:
REQUIRES_NEW 独立事务;
SUPPORTS 非事务环境下就是无事务;
NOT_SUPPORTED 强制以非事务运行。
java
// ❌ NOT_SUPPORTED:不开启事务,发生异常不会回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addUser(){
userMapper.insert(...);
int i=1/0;
}
5. 数据库底层不支持事务
MyISAM 引擎不支持事务,哪怕代码写对 @Transactional,也无法回滚。
查看表引擎:
show create table user;✅ 必须使用 InnoDB。
6. 多线程场景,事务失效
事务绑定在当前线程,新开子线程执行数据库操作,子线程是全新事务上下文,和主线程事务隔离。
java
@Transactional
public void test() {
userMapper.insertA();
new Thread(() -> {
userMapper.insertB();
int i=1/0; // ❌ 子线程异常,不会回滚insertA
}).start();
}
7. 事务超时时间,超时自动回滚(不是不生效,是触发回滚)
java
// 5秒超时,超时后自动回滚
@Transactional(timeout = 5)
public void test() throws InterruptedException {
userMapper.insert();
Thread.sleep(6000);
}
8. 注解被错误放在接口上(老版本 Spring)
Spring AOP 优先读取实现类上的注解;
只在接口上加 @Transactional,使用 CGLIB 代理时事务会失效。
最佳实践:
@Transactional写在 Service 实现类的 public 方法。
9. 嵌套事务,传播 NESTED,外层捕获异常导致内层不回滚
java
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional
public void main() {
userMapper.insertUser();
try {
// NESTED 嵌套事务
orderService.addOrder();
} catch (Exception e) {
log.error("子事务异常");
// ❌ 外层捕获异常,外层事务不回滚;内层子事务回滚
}
}
}
@Service
class OrderService{
@Transactional(propagation = Propagation.NESTED)
public void addOrder(){
orderMapper.insert();
int i=1/0;
}
}
总结
- AOP 代理问题:非 public、同类 this 调用
- 异常问题:吞异常、rollbackFor 没配置
- 数据库:MyISAM 引擎
- 传播属性:NOT_SUPPORTED/SUPPORTS 误用
- 线程:跨线程事务隔离
- 注解位置:仅接口上加注解(CGLIB)
扩展
Q:为什么同类 this 调用事务失效?
A:Spring 事务由 AOP 代理对象增强;this 是原始对象,没有经过代理,不会执行事务拦截器。
Q:rollbackFor=Exception.class 作用?A:默认只回滚 RuntimeException/Error;加上之后所有 Exception(包含受检异常)都会触发回滚。
Spring 事务传播行为(Propagation)
核心:多个事务方法相互调用时,事务如何传递,一共 7 种,
| 传播类型 | 含义 | 场景说明 |
|---|---|---|
| REQUIRED(默认) | 如果当前存在事务,就加入该事务;没有则新建一个事务 | 绝大多数业务,同一件事,要么全部成功要么全部回滚 |
| REQUIRES_NEW | 新建独立事务;如果当前有事务,挂起原有事务,执行新事务,互不影响 | 日志记录、独立审计,子事务失败不影响外层事务 |
| SUPPORTS | 当前有事务就加入;没有事务就以非事务方式执行 | 查询场景,可有可无事务 |
| MANDATORY | 必须运行在已有事务中;当前无事务直接抛异常 | 强制外层必须开启事务,防止单独调用 |
| NOT_SUPPORTED | 以非事务方式执行;当前有事务就挂起原有事务 | 大批量查询,不想占用事务资源 |
| NEVER | 强制非事务运行;如果当前存在事务,直接抛异常 | 禁止事务环境,只读统计 |
| NESTED | 嵌套事务:有事务则嵌套在当前事务内(保存点 savepoint);无事务新建事务 | 子事务回滚不会导致外层事务回滚;外层事务回滚,嵌套子事务一定回滚 |
重点区分
REQUIRES_NEW vs NESTED
- REQUIRES_NEW:两个完全独立事务,外层事务挂起。子事务提交后不受外层影响;子事务异常外层可以捕获继续提交。
- NESTED :同一个物理事务,基于保存点 savepoint。只有外层事务提交,嵌套事务才真正提交;外层回滚,嵌套一定回滚。
NESTED 不是独立事务,是保存点;
REQUIRES_NEW 是全新独立事务。
扩展
- 传播行为默认值?
REQUIRED - REQUIRES_NEW 挂起事务原理?Spring 事务管理器暂停当前事务,保存上下文,执行完恢复
- 哪些传播行为会创建新事务:
REQUIRED、REQUIRES_NEW、NESTED
Spring 7 种事务传播行为 + 代码演示 + 回滚规则
前提:
- AOP 代理调用才生效;同类方法内部调用事务失效。
@Transactional默认只对 RuntimeException / Error 触发回滚,受检异常默认不回滚。- 外层方法叫内层方法,外层 = 调用方,内层 = 被调用方。
公共基础代码(两个 Service,方便测试)
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
// 外层方法
public void outer(){}
}
@Service
public class InnerService {
// 内层方法
public void inner(){}
}
1. REQUIRED(默认)
有事务就加入当前事务;无事务新建事务。
外层内层是同一个事务
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
// 插入订单
innerService.inner();
// 外层抛异常
throw new RuntimeException("外层异常");
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.REQUIRED)
public void inner() {
// 扣库存
}
}
✅ 回滚规则:
- 外层抛异常 → 整个事务回滚,内层操作回滚
- 内层抛异常,外层不捕获 → 整个事务回滚
- 内层抛异常,外层 try-catch 接住:依然会回滚! 异常标记了事务为 rollback-only,外层 catch 后提交时报异常。
场景:正常业务,增删改,同业务要么全成要么全回。
2. REQUIRES_NEW
如果外层有事务,挂起外层事务,新建独立事务。两个完全独立物理事务,互不干扰。
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void outer() {
// 插入订单
try {
innerService.inner();
} catch (Exception e) {
// 捕获内层异常,外层继续跑
}
// 外层正常结束,外层事务提交
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 扣库存
throw new RuntimeException("内层异常");
}
}
✅ 回滚规则:
- 内层抛异常 → 内层事务回滚;外层事务不受影响,可以正常提交(try-catch 捕获内层异常)
- 外层抛异常 → 外层事务回滚;内层事务已经独立提交完成,不会回滚
重点:内层事务执行完就提交,外层事务被挂起,两者独立。 适用:操作日志、审计记录,就算主业务失败,日志也要入库。
3. SUPPORTS
当前有事务,就加入事务;没有事务,就非事务方式运行
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void outer() {
// 外层存在事务
innerService.inner();
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.SUPPORTS)
public void inner() {
// 查询或者更新
}
}
✅ 回滚规则:
- 外层有事务:内层加入外层事务,任意地方异常,整体回滚(和 REQUIRED 行为一致)
- 外层没有事务:内层无事务,发生异常不会回滚,已经执行的 SQL 持久化
适用:查询方法,有事务环境就参与事务,没有就普通查询。
4. MANDATORY
必须运行在已有事务中;外层没有事务直接抛异常,不会新建事务
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
// 外层不加 @Transactional
public void outer() {
innerService.inner(); // ❌ 直接抛出 IllegalTransactionStateException
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.MANDATORY)
public void inner() {
}
}
✅ 回滚规则:
- 外层存在事务:内层加入外层事务,统一回滚;
- 外层无事务:直接报错,方法不执行。
适用:强制该方法必须被事务方法调用,防止单独调用造成数据不一致。
5. NOT_SUPPORTED
以非事务方式执行;外层有事务就挂起外层事务,内层不在事务里
挂起(suspend) ≠ 提交 / 回滚外层事务只是临时把外层事务暂停、放到一边保存起来,然后执行当前这个无事务的方法;
等内层方法执行完毕之后,再恢复外层事务继续跑。
概括:
外层事务先暂停,内层裸跑(没有事务);内层跑完,再把外层事务捡回来继续执行。
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void outer() {
// 外层事务开启,调用内层时外层事务被挂起
innerService.inner();
// 内层执行完,恢复外层事务
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void inner() {
// 内层无事务,这里抛异常,内层SQL不会回滚
throw new RuntimeException();
}
}
✅ 回滚规则:
- 内层代码抛异常:内层操作不会回滚(无事务);外层捕获异常后,外层事务继续提交;外层没捕获,外层事务回滚。
适用:大批量查询,大报表,不想占用事务连接。
6. NEVER
禁止在事务中执行;外层存在事务直接抛异常,必须非事务环境
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void outer() {
innerService.inner(); // ❌ 抛出 IllegalTransactionStateException
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.NEVER)
public void inner() {
}
}
✅ 回滚规则:
- 外层有事务:直接报错;
- 外层无事务:普通非事务执行,异常不会回滚。
适用:统计任务,明确禁止事务环境。
7. NESTED 嵌套事务
外层有事务:基于 JDBC Savepoint 保存点,嵌套在同一个物理事务;外层无事务新建事务。 ⚠️ 和 REQUIRES_NEW 最大区别:同一个物理事务,不是独立事务
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void outer() {
// 外层事务开启,创建savepoint
try {
innerService.inner();
} catch (Exception e) {
// 捕获内层异常,只回滚到savepoint,外层事务还能继续提交
System.out.println("内层异常,但是外层可以提交");
}
// 外层正常执行,最终提交
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.NESTED)
public void inner() {
throw new RuntimeException("内层嵌套异常");
}
}
✅ 回滚规则:
- 内层抛异常,外层 try-catch 捕获:只回滚内层操作,外层事务可以正常提交 ✅
- 外层抛异常 :无论内层是否成功,外层整体回滚,内层 savepoint 一起回滚
底层:
JDBC Savepoint,不是独立事务。MySQL InnoDB 支持。
适用:
主流程不能回滚,但子步骤失败可以单独撤销子步骤。
🔥 核心对比总结表
| 传播行为 | 外层有事务 | 外层无事务 | 独立事务 | 内层异常 (外层 catch) | 外层异常 |
|---|---|---|---|---|---|
| REQUIRED | 加入外层事务 | 新建事务 | 否 | 标记 rollback-only,整体回滚 | 整体回滚 |
| REQUIRES_NEW | 挂起外层,新建独立事务 | 新建事务 | 是 | 内层回滚,外层可提交 | 外层回滚,内层已提交不受影响 |
| SUPPORTS | 加入外层事务 | 非事务执行 | 否 | 整体回滚 | 整体回滚 |
| MANDATORY | 加入外层事务 | 抛异常 | 否 | 整体回滚 | 整体回滚 |
| NOT_SUPPORTED | 挂起外层,内层非事务 | 非事务执行 | 否 | 内层不回滚 | 外层回滚,内层已持久化 |
| NEVER | 抛异常 | 非事务执行 | 否 | 无机会执行 | 无机会执行 |
| NESTED | 创建 savepoint 嵌套 | 新建事务 | 否 | 仅回滚 savepoint,外层可提交 | 外层整体回滚 |