spring 中的事务(Transaction)与传播行为(Propagation)

78 阅读5分钟

Spring 事务的实现要点(高层)

Taimili 艾米莉 ( 一款免费开源的 taimili.com )

艾米莉 是一款优雅便捷的 GitHub Star 管理和提升工具,基于 PHP & javascript 构建, 能对github 的 star fork follow watch 管理和提升,最适合github 的深度用户

WechatIMG69.jpg

  • Spring 的声明式事务(@Transactional)通过 AOP(事务拦截器)实现:对标注方法创建代理,代理在方法执行前开启/获取事务,方法执行后提交或回滚。
  • 事务管理核心类PlatformTransactionManager(接口),常见实现:DataSourceTransactionManager(JDBC)、JpaTransactionManager(JPA/Hibernate)、JtaTransactionManager(分布式/容器事务)。
  • 事务资源通过 TransactionSynchronizationManager 绑定到当前线程(thread-bound)。

事务属性(TransactionDefinition)常用项

  • propagation:传播行为(下面详细)。
  • isolation:隔离级别(READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)。
  • readOnly:优化提示,通常对写操作无效。
  • timeout:超时时间。
  • rollbackFor / noRollbackFor:指定回滚/不回滚的异常类型。

事务回滚规则(默认)

  • 默认仅对 RuntimeException(unchecked)或 Error 回滚
  • 对 checked exception(受检异常)默认不回滚。若需回滚 checked exception,需 @Transactional(rollbackFor = Exception.class) 或指定具体异常。

传播行为(Propagation)详解(关键)

Spring 中 Propagation 常用枚举及语义:

  • REQUIRED(默认)
    • 如果存在当前事务:加入该事务(使用同一个事务)。
    • 如果不存在:创建新事务。
    • 示例:A(tx REQUIRED) 调用 B(tx REQUIRED) → A 与 B 在同一事务,B 抛出运行时异常会导致整个事务回滚(包括 A 的修改)。
  • REQUIRES_NEW
    • 总是挂起当前事务(如果存在),并创建一个新事务;方法结束后,恢复挂起事务。
    • 示例:A(tx REQUIRED) 调用 B(tx REQUIRES_NEW) → B 在独立事务,B 的回滚不会影响 A(除非 A 自身也回滚或捕获/处理异常逻辑影响)。
    • 注意开销:会执行事务挂起/恢复,成本较高。
  • SUPPORTS
    • 如果存在事务则加入;如果没有则以非事务方式运行(不创建新事物)。
    • 适合既可在事务内工作,也可在事务外工作的场景。
  • NOT_SUPPORTED
    • 总是以非事务方式运行;如果当前存在事务,则先挂起当前事务。
    • 适用于必须在无事务上下文中执行的逻辑(例如某些外部调用不能在事务中执行)。
  • MANDATORY
    • 必须在已有事务中执行;如果没有事务则抛异常(IllegalTransactionStateException)。
    • 用于强制要求事物的内部方法。
  • NEVER
    • 必须在无事务中运行;如果存在事务则抛异常。
  • NESTED
    • 如果存在当前事务:在物理事务内部使用 savepoint(保存点) ,可以局部回滚到保存点而不影响外层事务(前提:底层 JDBC 驱动和 DataSourceTransactionManager 支持)。
    • 如果没有当前事务:行为同 REQUIRED(创建新事务)。
    • 注意:NESTED 依赖 JDBC 的 savepoint 支持,因此在 JTA 或某些事务管理器下可能不可用或行为不同。

典型场景示例

代码:

1
@Servicepublic class OuterService {    @Autowired InnerService inner;    @Transactional // propagation = REQUIRED    public void outer() {        // DB 操作 A        inner.requiresNew(); // REQUIRES_NEW        // DB 操作 B    }}@Servicepublic class InnerService {    @Transactional(propagation = Propagation.REQUIRES_NEW)    public void requiresNew() {        // DB 操作 C        throw new RuntimeException("fail inner"); // 回滚 inner 事务,不影响 outer 的已提交动作(除非 outer 回滚)    }}

行为说明:

  • inner.requiresNew() 在独立事务中;throw 导致 inner 的事务回滚
  • outer 的事务如果捕获异常并继续提交,则 outer 提交(inner 的回滚不影响 outer)。
  • 若 inner 使用 REQUIRED,则 inner 的异常会导致 outer 的整个事务回滚(因为是同一个事务)。

关于 NESTED 的补充

  • NESTED 在同一个物理事务上使用 savepoint,因此 inner 抛异常可以回滚到 savepoint 而不回滚整个物理事务,前提是使用 DataSourceTransactionManager 且底层 JDBC 支持 savepoint。

@Transactional 的实现细节(与 AOP 关系)

  • @Transactional 是通过 TransactionalAnnotationParser 解析 TransactionAttribute,由 TransactionInterceptor(MethodInterceptor)在代理处拦截。拦截器调用 PlatformTransactionManager.getTransaction(), 然后 proceed(),最后根据异常决定 commit() 或 rollback()
  • 因为基于代理:同类内部方法调用(self-invocation)不会触发事务。解决办法同 AOP:外部代理调用或使用 AspectJ。

常见事务陷阱与 Best Practices

  • 不要把 @Transactional 放在 private/final 方法或类上(代理拦截不了);一般放在 public 方法和 service 层。
  • 自调用(self-invocation)不会走事务,把事务边界放在不同 bean 的方法上或使用 AspectJ。
  • 异常回滚规则:默认仅对运行时异常回滚,需对受检异常回滚时指定 rollbackFor
  • 事务不要跨越线程边界(事务是线程绑定的)。如果需要异步/多线程,请在新线程中重新打开事务或用消息队列设计。
  • 事务粒度要小:避免在事务中做网络 I/O、文件上传、长时间计算,降低锁竞争与阻塞时间。
  • 尽量在 Service 层控制事务,DAO 层只做细粒度 DB 操作。

常见问题速查(Cheat sheet)

  • @Transactional 不生效?常见原因:a) 方法为 private 或内部自调用;b) 注解在类的私有方法或 final 类;c) 代理类型问题(接口 vs 类);d) 事务管理器未配置或 Bean 未被 Spring 管理。
  • 要回滚 checked exception?@Transactional(rollbackFor = Exception.class)
  • 要在内部方法使用事务但避免 self-invocation?把内部方法抽到另一个 @Service bean。
  • 想用真正的编译/类加载器织入 AOP(可拦截构造函数、自调用等)?使用 AspectJ(需要 aop.xml / LTW 或编译时织入)。

小结(要记住的关键点)

  • IoC:容器负责实例化和注入依赖,推荐 构造器注入
  • AOP(Spring) :基于代理、运行时织入、适合方法级拦截,注意自调用与 final/private 的限制。
  • Bean 生命周期:BeanDefinition → 实例化 → 属性注入 → Aware 回调 → postProcessBefore → 初始化方法 → postProcessAfter → 可用;销毁有对应回调。
  • 事务传播REQUIRED(默认)与 REQUIRES_NEW 是最常用;理解“加入现有事务/挂起并新建事务/非事务运行”的区别非常重要。
  • 事务与 AOP 密切相关@Transactional 的实现就是用 AOP 拦截器,所有 AOP 的代理限制同样适用于事务。