只会背隔离级别还不够,Java 面试更爱问:
- 为什么
@Transactional没生效? - 为什么没回滚?
- 传播行为怎么选?
你必须记住的 3 句话(面试直出):
- Spring 事务的本质是 AOP 代理 + 在方法边界管理连接与提交/回滚。
@Transactional最常见失效原因是 自调用(同类方法互调)绕过代理。- 回滚默认只对 RuntimeException/Error,想对受检异常回滚需要显式配置。
1. Spring 事务在做什么:把"事务边界"绑在方法上
当你写:
@Transactional public void foo(){...}
Spring 通过代理拦截调用,在方法前后做:
- 开启事务(拿连接、设置隔离级别/只读等)
- 执行业务
- 发生异常按规则回滚,否则提交
面试加分表达:
- Spring 事务不是数据库事务的替代品,它只是 把 JDBC 事务的 begin/commit/rollback 管理自动化。
2. 传播行为:不要背全套,先抓住 3 个最常用
2.1 REQUIRED(默认)
- 有事务就加入,没有就新建
- 适用:绝大多数业务服务方法
2.2 REQUIRES_NEW
- 总是新开一个事务,挂起外层事务
- 适用:
- 你希望"外层失败不影响内层落库"(例如记录审计日志)
- 或希望内层失败可独立回滚
风险提示:
- 使用不当会导致事务碎片化、吞吐下降、锁冲突更复杂
2.3 NESTED
- 基于 savepoint(保存点)的嵌套事务
- 适用:部分回滚,但仍希望跟随外层一起提交
注意:
- 是否真正生效与数据库/事务管理器能力有关
3. 最常见的"事务失效"清单(线上 80% 都在这)
- 失效 1:自调用绕过代理
- 同一个类里 A 方法调用 B 方法,B 上的
@Transactional不生效。
- 同一个类里 A 方法调用 B 方法,B 上的
典型长相(你线上真的会遇到):
createOrder()里直接调用同类的saveOrder(),而saveOrder()才标了@Transactional
更稳的修复方式(按推荐顺序):
-
拆分 Bean:把需要事务的方法挪到另一个 Service,让调用经过代理(最推荐)
-
自注入代理调用:通过注入自身的代理对象来调用(需要小心循环依赖与可读性)
-
不推荐的技巧 :依赖
AopContext.currentProxy()(侵入性强,容易踩配置坑) -
失效 2:方法不是 public
- 常见代理策略下,非 public 可能无法被代理。
-
失效 3:异常被吞掉
- catch 了异常但没抛出,Spring 认为正常结束就会提交。
-
失效 4:多数据源/多事务管理器没指定
- 事务开在 A 库,SQL 却跑到 B 库。
-
失效 5:异步/新线程
- 新线程不继承线程绑定的连接与事务上下文。
4. 回滚规则:为什么没回滚
默认规则:
- RuntimeException / Error -> 回滚
- Checked Exception -> 不回滚
你应该能顺口说出:
- 受检异常需要
rollbackFor = Exception.class才会回滚。
常见"看似回滚但其实没回滚"的场景:
- 事务传播把内部事务独立提交(
REQUIRES_NEW) - 你在外层 catch 了异常并吞掉
再补一个高频线上边界(尤其是"写库 + 发消息/写 MQ"):
- 事务回滚只能回滚数据库,回滚不了已经发出去的消息/调用出去的 RPC
- 如果你用
REQUIRES_NEW让"日志/消息"独立提交,要能接受:外层失败时日志/消息仍然存在
工程建议:
- 需要强一致的"落库 + 发消息",优先考虑 Outbox/本地消息表/CDC 这类模式,而不是靠传播行为硬凑
5. 实战最佳实践(把事务写得更稳)
-
原则 1:缩短事务
- 不要在事务里做 RPC/大 IO/长时间计算。
-
原则 2:一类方法一个事务边界
- 把"纯查询/纯计算"与"写库"分开。
-
原则 3:对外部接口做幂等
- 事务回滚只能回滚数据库,回滚不了外部副作用。
-
原则 4:明确只读事务
- 读多场景可以
readOnly=true(配合连接/数据库优化),但不要迷信它能解决锁问题。
- 读多场景可以
-
原则 5:必要时做重试,但要识别死锁/锁等待
- 死锁回滚是正常机制,但重试必须保证幂等。
6. 线上排查:事务相关问题怎么定位
-
现象 1:数据没回滚
- 看异常类型是否触发回滚
- 看是否异常被 catch
- 看是否自调用绕过代理
-
现象 2:接口变慢/超时
- 先按"锁等待/慢 SQL"排查
- 再看是否事务过长导致持锁时间长
-
现象 3:部分数据提交、部分没提交
- 重点检查传播行为(尤其是
REQUIRES_NEW)与多数据源
- 重点检查传播行为(尤其是
7. 自测清单(你要能顺口讲出来)
-
Q:
@Transactional为什么经常失效?- A:因为事务是代理生效,最常见是同类自调用绕过代理。
-
Q:为什么抛了异常却没回滚?
- A:默认只回滚运行时异常;受检异常需要配置
rollbackFor。
- A:默认只回滚运行时异常;受检异常需要配置
-
Q:
REQUIRES_NEW适合什么场景?- A:需要独立提交/回滚的小事务,例如审计日志;但会增加复杂度与冲突。
8. 30 秒背诵稿
Spring 事务本质是 AOP 代理在方法边界自动管理 JDBC 事务,最常见失效是自调用绕过代理。传播行为里 REQUIRED 默认加入或新建事务,REQUIRES_NEW 会挂起外层并开启独立事务,NESTED 基于保存点做部分回滚。回滚默认只对 RuntimeException/Error 生效,受检异常需要 rollbackFor。线上问题优先检查代理调用路径、异常是否被吞、传播行为与多数据源绑定,再结合锁等待/慢 SQL 判断是否长事务导致抖动。