Skip to content

事务拦截器

This content is not available in your language yet.

声明式事务把一次方法调用包进“属性解析、事务创建、目标执行、异常决策、提交/回滚、线程上下文清理”的完整围绕通知。

先给答案:声明式事务把“业务调用”包成一个可提交或回滚的资源边界

Section titled “先给答案:声明式事务把“业务调用”包成一个可提交或回滚的资源边界”

事务拦截器并不替业务决定每条 SQL,而是在方法调用前绑定资源、判断传播行为,调用成功后提交,异常时回滚。事务管理器负责把抽象事务映射到 JDBC、JPA 等具体资源,业务代码只看到统一边界。

传播行为的难点在于嵌套调用:加入已有事务、挂起事务、创建新事务和保存点分别改变了资源与回滚范围。自调用不生效、异常被吞掉、异步线程丢失事务上下文,本质都是调用没有经过原代理或执行线程已经脱离原资源边界。

proxy.invoke
-> TransactionInterceptor#invoke
-> invokeWithinTransaction
-> getTransaction
-> target method
├─ success -> commit
└─ error -> rollback rules -> rollback/commit
-> cleanupTransactionInfo

TransactionInterceptor#invoke() — spring-tx/.../TransactionInterceptor.java:119;核心 TransactionAspectSupport#invokeWithinTransaction() — spring-tx/.../TransactionAspectSupport.java:333。

TransactionAttributeSource 根据方法和目标类解析传播行为、隔离级别、超时和 rollback 规则;determineTransactionManager() 选择具体 PlatformTransactionManager。拦截器不直接操作 JDBC,而是依赖事务管理器策略。

createTransactionIfNecessary() 建立 TransactionInfo,invocation.proceedWithInvocation() 执行目标;异常进入 completeTransactionAfterThrowing(),正常返回进入 commitTransactionAfterReturning(),最后无论成功失败都执行 cleanupTransactionInfo()。

替代方案:每个业务方法手工 begin/commit/rollback。 为什么不行:异常路径、嵌套传播、线程绑定和多种资源实现会在业务代码重复,且很容易遗漏 finally 清理。 证据:TransactionAspectSupport 统一管理 TransactionInfo 和线程本地上下文,TransactionInterceptor 只负责把 AOP invocation 适配到该模板。

当事务管理器是 ReactiveTransactionManager 时,invokeWithinTransaction() 根据返回类型选择 reactive adapter,而不是把事务状态绑定到当前调用线程。同步事务和响应式事务因此不能只靠替换管理器类型理解。

场景 现象 原因 规避
private 方法加 @Transactional 不生效 代理通常只拦截可见的外部调用 放到 public 协作者或改代理策略
捕获异常后不抛出 事务提交 拦截器看不到失败 明确 rollbackFor 或重新抛出
异步返回未完成 事务已提交 方法返回不等于异步任务完成 使用可传播的事务模型

围绕通知适合实现重试、限流、审计和权限;但必须明确“边界覆盖的是方法调用,还是异步工作的完整生命周期”。

面试锚点

  • @Transactional 的提交和回滚由谁决定?
  • 为什么 self-invocation 不触发事务?
  • 响应式事务为什么不能依赖 ThreadLocal?