Spring 声明式事务通常通过 AOP 代理实现。先理解代理怎样包住方法,再判断事务为什么没有按预期生效,比孤立背“失效场景列表”更可靠。
AOP 解决什么问题?
AOP 把日志、事务、权限等横切逻辑从业务方法里抽出来。切面定义增强逻辑,切点描述哪些方法匹配,通知描述何时执行。
调用方 → Spring 代理 → 开始事务 → 目标业务方法 ├─ 正常:提交 └─ 满足回滚规则的异常:回滚JDK 代理基于接口,CGLIB 代理基于子类,实际选择受接口情况、框架配置与使用场景影响。CGLIB 无法通过重写增强 final/private 方法,也不能继承 final 类。基础上不需要背通知类的全部接口。
@Transactional 的基本用法
@Transactional(rollbackFor = Exception.class)public void createOrder() { orderRepository.insert(); stockRepository.deduct();}在同一个事务管理器协调的数据库事务内,两步可以一起提交或回滚。一个本地事务不会自动把远程 HTTP 调用、另一个数据库或消息发送也变成同一个原子操作。
传统默认回滚规则是遇到 RuntimeException 或 Error 回滚,检查型异常默认不回滚。可以用 rollbackFor 等配置;项目也可能配置不同的全局规则,回答时以实际配置为准。
readOnly 表示只读意图或优化提示,不是所有数据库、驱动与事务管理器下都强制禁止写入。隔离级别解决数据库并发读写问题,传播行为解决调用链已有事务时怎样参与,二者不同。
常用传播行为
| 行为 | 基础语义 |
|---|---|
| REQUIRED | 有事务就加入,没有就创建,默认常用 |
| REQUIRES_NEW | 挂起当前事务,使用独立事务 |
| NESTED | 在支持的情况下用保存点提供嵌套回滚,不是独立物理事务 |
| SUPPORTS | 有事务就参与,没有则无事务执行 |
| NOT_SUPPORTED | 无事务执行,必要时挂起已有事务 |
| MANDATORY | 必须已有事务,否则报错 |
| NEVER | 不允许已有事务,否则报错 |
优先记前三个。REQUIRES_NEW 内部成功后,外层失败通常不会撤销它的提交,但会消耗独立事务资源;NESTED 的最终提交仍依赖外层事务,并受事务管理器支持限制。
常见失效或回滚不符预期的原因
- 绕过代理:对象自己 new,或同类方法通过 this 自调用事务方法,未经过代理。
- 方法不可被当前代理增强:例如 private,或 CGLIB 的 final 方法。不同版本对非 public 方法的支持不同,基础项目优先用可通过代理调用的 public 服务方法。
- 异常被吞掉:业务 catch 后正常返回,代理看不到原异常,也未显式标记回滚。
- 规则不匹配:抛检查型异常却没有配置相应回滚规则。
- 换了线程或资源:普通线程绑定的事务不会自动传到新线程;操作也可能未使用相应事务资源。
“自调用不生效”准确地说是内层方法自己的事务拦截没有执行。若外层已通过代理开启事务,内层数据库操作仍可能处在外层事务中,但内层的 REQUIRES_NEW 等配置不会因此自动生效。
如果参与同一事务的内层逻辑标记了 rollback-only,外层 catch 异常也不一定能够继续提交,可能最终出现 UnexpectedRollbackException。
面试回答模板
Spring AOP 通常通过代理在目标方法前后执行增强,声明式事务就在代理层开启、提交或回滚。默认传播行为 REQUIRED,加入已有事务或新建事务;REQUIRES_NEW 独立提交,NESTED 在支持时使用保存点。判断不生效要检查是否经过代理、方法能否增强、异常与回滚规则,以及线程和资源是否在同一事务上下文中。
自测
- 同类自调用的内层 REQUIRES_NEW 会自动开启独立事务吗?不会,默认代理模式下绕过拦截。
- catch 检查型异常后正常返回一定回滚吗?不一定。
- 本地 @Transactional 能保证数据库写入与 HTTP 请求原子吗?不能。