news 2026/10/8 7:08:44

一文详解Spring 事务失效场景

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文详解Spring 事务失效场景

前提:

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

总结

  1. AOP 代理问题:非 public、同类 this 调用
  2. 异常问题:吞异常、rollbackFor 没配置
  3. 数据库:MyISAM 引擎
  4. 传播属性:NOT_SUPPORTED/SUPPORTS 误用
  5. 线程:跨线程事务隔离
  6. 注解位置:仅接口上加注解(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

  1. REQUIRES_NEW:两个完全独立事务,外层事务挂起。子事务提交后不受外层影响;子事务异常外层可以捕获继续提交。
  2. NESTED:同一个物理事务,基于保存点 savepoint。只有外层事务提交,嵌套事务才真正提交;外层回滚,嵌套一定回滚。

NESTED 不是独立事务,是保存点;

REQUIRES_NEW 是全新独立事务。

扩展

  1. 传播行为默认值?REQUIRED
  2. REQUIRES_NEW 挂起事务原理?Spring 事务管理器暂停当前事务,保存上下文,执行完恢复
  3. 哪些传播行为会创建新事务:REQUIRED、REQUIRES_NEW、NESTED

Spring 7 种事务传播行为 + 代码演示 + 回滚规则

前提:

  1. AOP 代理调用才生效;同类方法内部调用事务失效。
  2. @Transactional默认只对RuntimeException / Error触发回滚,受检异常默认不回滚。
  3. 外层方法叫内层方法,外层 = 调用方,内层 = 被调用方。

公共基础代码(两个 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("内层异常"); } }

✅ 回滚规则:

  1. 内层抛异常 →内层事务回滚;外层事务不受影响,可以正常提交(try-catch 捕获内层异常)
  2. 外层抛异常 →外层事务回滚;内层事务已经独立提交完成,不会回滚

重点:内层事务执行完就提交,外层事务被挂起,两者独立。 适用:操作日志、审计记录,就算主业务失败,日志也要入库。


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("内层嵌套异常"); } }

✅ 回滚规则:

  1. 内层抛异常,外层 try-catch 捕获:只回滚内层操作,外层事务可以正常提交 ✅
  2. 外层抛异常:无论内层是否成功,外层整体回滚,内层 savepoint 一起回滚

底层:

JDBC Savepoint,不是独立事务。MySQL InnoDB 支持。

适用:

主流程不能回滚,但子步骤失败可以单独撤销子步骤。


🔥 核心对比总结表

传播行为外层有事务外层无事务独立事务内层异常 (外层 catch)外层异常
REQUIRED加入外层事务新建事务否标记 rollback-only,整体回滚整体回滚
REQUIRES_NEW挂起外层,新建独立事务新建事务是内层回滚,外层可提交外层回滚,内层已提交不受影响
SUPPORTS加入外层事务非事务执行否整体回滚整体回滚
MANDATORY加入外层事务抛异常否整体回滚整体回滚
NOT_SUPPORTED挂起外层,内层非事务非事务执行否内层不回滚外层回滚,内层已持久化
NEVER抛异常非事务执行否无机会执行无机会执行
NESTED创建 savepoint 嵌套新建事务否仅回滚 savepoint,外层可提交外层整体回滚
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 7:08:43

《创业之路》-1020-细读商业经典 - 商业的本质是一场经济博弈:利用和借用资源,创造价值,与竞争对手争取客户,获取客户经济回报的一场经济博弈,是在规则范围内的竞争、甚至是你死我活斗争。

商业的完整真相:规则框架下的价值博弈,与极限处的生存竞争这句话比单一的 “价值创造论” 更接近商业的真实全貌:商业本质上是一场规则边界内的经济博弈—— 以资源整合为手段、以价值创造为基础、以客户争夺为核心、以经济回报为闭环&#x…

作者头像 李华
网站建设 2026/10/8 7:08:36

云身份权限风险,AK 泄露危害与检测

云身份权限风险,AK 泄露危害与检测 前言 随着企业业务全面上云,阿里云、腾讯云、华为云、AWS 等云平台成为企业 IT 基础设施核心。传统内网边界逐渐模糊,云身份凭证 AccessKey(AK/SK) 已经成为云上最高权限凭证&…

作者头像 李华
网站建设 2026/10/8 7:06:39

漫剧角色资产库分层源文件的3级角色拆解标准与图层命名对照

漫剧角色资产库的分层源文件应该如何制作?核心路径是:按角色三级分级决定分层颗粒度,沿关节切割而非按色块分组,图层统一英文下划线命名,口型viseme和表情拆成独立PSD插槽文件。截至2026年,竖屏漫剧主流画布…

作者头像 李华