1. 首页
  2. 面试专题
  3. 文章列表
多家公司 Java 后端 Spring 事务边界 2026-06-14

Spring 事务失效通常不是数据库问题,而是代理边界没想清楚

Spring 事务失效常见于自调用、异常类型、传播行为和异步线程边界。面试里要把代理机制和业务一致性一起讲清楚。

Spring 事务题很容易被答成“加一个 @Transactional 就行”。但真实面试里,面试官往往会继续问:为什么注解加了却没回滚?为什么同一个类里调用事务方法不生效?为什么一个方法回滚会把外层也带回去?这时如果只说数据库支持事务,就已经偏题了。

Spring 声明式事务的关键在代理。你调用的不是一个带魔法的普通方法,而是经过代理对象织入了开启事务、提交、回滚等逻辑。理解这个边界,很多“事务失效”问题就能解释清楚。

自调用为什么容易失效

如果一个类里的方法 A 直接调用本类方法 B,调用路径没有经过 Spring 代理对象,B 上的事务增强就可能不会执行。表现上看像是注解失效,本质是调用边界不对。

解决方式不应该机械背“把方法拆到另一个 Service”。更准确的取舍是:如果 B 确实是独立事务语义,把它抽成另一个 Bean 是合理的;如果只是为了让注解生效而拆,说明业务边界可能还没想清楚。事务边界最好和业务动作一致,比如创建订单、扣减库存、写流水,而不是围绕工具方法随意切。

回滚规则不是所有异常都一样

默认情况下,Spring 对运行时异常和 Error 回滚,对受检异常不一定回滚。很多项目里远程调用、文件处理、业务校验会抛出各种异常,如果没有明确 rollbackFor,可能出现代码抛错但事务提交的情况。

面试里可以把这点讲成工程习惯:事务方法要明确哪些异常代表业务失败,哪些异常可以被捕获后转成失败状态。如果 catch 了异常却不重新抛出,事务管理器可能认为方法正常结束,就会提交。这不是数据库的问题,是异常语义被吞掉了。

传播行为要看业务关系

REQUIRED 是最常见的传播行为,能加入外层事务就加入,没有就新建。REQUIRES_NEW 会挂起外层事务,开启一个新事务。NESTED 依赖数据库保存点能力,和 REQUIRES_NEW 不是一回事。

这些概念放到业务里才有意义。订单主流程失败时,订单数据应该回滚;但审计日志、失败流水、风控记录可能希望独立保存,用 REQUIRES_NEW 才合理。反过来,如果把核心库存扣减放进新事务,外层订单失败后库存已经提交,就可能制造更难处理的不一致。

异步线程是另一个常见边界

把任务丢到线程池、发事件、用 @Async,都会让执行线程发生变化。事务上下文通常绑定在线程上,不能默认跨线程延续。外层事务还没提交,异步任务就去读数据,可能读不到;外层事务最终回滚,异步任务却已经发了消息或写了日志,也会出问题。

因此事务和异步要搭配事件发布、消息表、事务提交后回调或补偿机制设计。不能把“异步提高性能”和“事务保证一致性”混在一起想。

回答时要从一致性目标出发

一段成熟回答可以这样组织:先说明 Spring 事务基于代理增强,调用必须经过代理边界;再说明回滚依赖异常传播和 rollback 规则;然后结合业务选择传播行为;最后补充异步、消息和外部系统不能天然参与本地事务,需要用最终一致或补偿闭环。

这样讲,事务就不只是一个注解,而是 Java 后端系统里最核心的一致性设计。