前提:
Spring 事务底层依靠AOP 动态代理,只有代理对象调用方法才会触发事务增强;
同时事务生效需要满足数据库引擎支持事务(InnoDB,MyISAM 不支持)。
注解:
@Transactional
1. 方法不是 public(最常见)
Spring AOP 只拦截public方法,private/protected/default方法加注解完全无效。
@Service public class UserService { // ❌ private,事务失效 @Transactional private void addUser() { userMapper.insert(...); int i = 1/0; // 抛异常不会回滚 } }2. 同类内部方法调用(this 调用,不走代理)
在同一个类中,直接this.xxx()调用带@Transactional的方法,不是代理对象调用,AOP 不会切入。
@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 吃掉异常
@Transactional public void addUser() { try { userMapper.insert(...); int i = 1/0; } catch (Exception e) { // ❌ 异常被捕获,没有抛出给AOP,事务不会回滚 log.error("error", e); } }场景 3.2 抛出受检异常,没指定 rollbackFor
@Transactional public void addUser() throws IOException { userMapper.insert(...); throw new IOException("IO异常"); // ❌ IOException是受检异常,默认不回滚 }✅ 修复:@Transactional(rollbackFor = Exception.class)
4. 传播行为配置错误
典型:
REQUIRES_NEW独立事务;
SUPPORTS非事务环境下就是无事务;
NOT_SUPPORTED 强制以非事务运行。
// ❌ 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. 多线程场景,事务失效
事务绑定在当前线程,新开子线程执行数据库操作,子线程是全新事务上下文,和主线程事务隔离。
@Transactional public void test() { userMapper.insertA(); new Thread(() -> { userMapper.insertB(); int i=1/0; // ❌ 子线程异常,不会回滚insertA }).start(); }7. 事务超时时间,超时自动回滚(不是不生效,是触发回滚)
// 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,外层捕获异常导致内层不回滚
@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,方便测试)
@Service public class OuterService { @Autowired private InnerService innerService; // 外层方法 public void outer(){} } @Service public class InnerService { // 内层方法 public void inner(){} }1. REQUIRED(默认)
有事务就加入当前事务;无事务新建事务。
外层内层是同一个事务
@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
如果外层有事务,挂起外层事务,新建独立事务。两个完全独立物理事务,互不干扰。
@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
当前有事务,就加入事务;没有事务,就非事务方式运行
@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
必须运行在已有事务中;外层没有事务直接抛异常,不会新建事务
@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) ≠ 提交 / 回滚外层事务
只是临时把外层事务暂停、放到一边保存起来,然后执行当前这个无事务的方法;
等内层方法执行完毕之后,再恢复外层事务继续跑。
概括:
外层事务先暂停,内层裸跑(没有事务);内层跑完,再把外层事务捡回来继续执行。
@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
禁止在事务中执行;外层存在事务直接抛异常,必须非事务环境
@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 最大区别:同一个物理事务,不是独立事务
@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,外层可提交 | 外层整体回滚 |