事务拦截器
声明式事务把一次方法调用包进“属性解析、事务创建、目标执行、异常决策、提交/回滚、线程上下文清理”的完整围绕通知。
先给答案:声明式事务把“业务调用”包成一个可提交或回滚的资源边界
Section titled “先给答案:声明式事务把“业务调用”包成一个可提交或回滚的资源边界”事务拦截器并不替业务决定每条 SQL,而是在方法调用前绑定资源、判断传播行为,调用成功后提交,异常时回滚。事务管理器负责把抽象事务映射到 JDBC、JPA 等具体资源,业务代码只看到统一边界。
传播行为的难点在于嵌套调用:加入已有事务、挂起事务、创建新事务和保存点分别改变了资源与回滚范围。自调用不生效、异常被吞掉、异步线程丢失事务上下文,本质都是调用没有经过原代理或执行线程已经脱离原资源边界。
proxy.invoke -> TransactionInterceptor#invoke -> invokeWithinTransaction -> getTransaction -> target method ├─ success -> commit └─ error -> rollback rules -> rollback/commit -> cleanupTransactionInfoTransactionInterceptor#invoke() — spring-tx/.../TransactionInterceptor.java:119;核心 TransactionAspectSupport#invokeWithinTransaction() — spring-tx/.../TransactionAspectSupport.java:333。
属性与管理器
Section titled “属性与管理器”TransactionAttributeSource 根据方法和目标类解析传播行为、隔离级别、超时和 rollback 规则;determineTransactionManager() 选择具体 PlatformTransactionManager。拦截器不直接操作 JDBC,而是依赖事务管理器策略。
标准同步路径
Section titled “标准同步路径”createTransactionIfNecessary() 建立 TransactionInfo,invocation.proceedWithInvocation() 执行目标;异常进入 completeTransactionAfterThrowing(),正常返回进入 commitTransactionAfterReturning(),最后无论成功失败都执行 cleanupTransactionInfo()。
为什么不把事务写进业务方法
Section titled “为什么不把事务写进业务方法”替代方案:每个业务方法手工 begin/commit/rollback。
为什么不行:异常路径、嵌套传播、线程绑定和多种资源实现会在业务代码重复,且很容易遗漏 finally 清理。
证据:TransactionAspectSupport 统一管理 TransactionInfo 和线程本地上下文,TransactionInterceptor 只负责把 AOP invocation 适配到该模板。
当事务管理器是 ReactiveTransactionManager 时,invokeWithinTransaction() 根据返回类型选择 reactive adapter,而不是把事务状态绑定到当前调用线程。同步事务和响应式事务因此不能只靠替换管理器类型理解。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
private 方法加 @Transactional |
不生效 | 代理通常只拦截可见的外部调用 | 放到 public 协作者或改代理策略 |
| 捕获异常后不抛出 | 事务提交 | 拦截器看不到失败 | 明确 rollbackFor 或重新抛出 |
| 异步返回未完成 | 事务已提交 | 方法返回不等于异步任务完成 | 使用可传播的事务模型 |
围绕通知适合实现重试、限流、审计和权限;但必须明确“边界覆盖的是方法调用,还是异步工作的完整生命周期”。
面试锚点
@Transactional的提交和回滚由谁决定?- 为什么 self-invocation 不触发事务?
- 响应式事务为什么不能依赖 ThreadLocal?