记一次事务传播机制的bug

最近用网上开源项目芋道做公司业务的时候,偶尔复现一个bug(一步步debug很久),本人负责权限这一块,涉及各种用户权限继承转移,一个逻辑里面涉及权限的增删改,因此事务的一致性很重要,在这里重新回顾一下事务传播机制

Spring 事务传播机制方案总结

Spring 事务传播行为(Propagation Behavior)定义了客户端代码如何与现有事务边界进行交互。以下是主要的传播机制及其详细说明:

传播行为 描述 作用 存在事务 (TxA) 不存在事务 发生异常时的回滚
REQUIRED (默认) 如果当前存在事务 TxA,则加入该事务;如果不存在,则创建一个新事务 TxB 确保业务方法始终运行在一个事务上下文中。 加入 TxA 创建新事务 TxB TxATxB 标记为 rollback-only,并最终回滚。
SUPPORTS 如果当前存在事务 TxA,则加入该事务;如果不存在,则以非事务方式执行。 支持事务,但非强制性。通常用于查询操作。 加入 TxA 非事务执行。 如果加入 TxA,TxA 会回滚。如果非事务执行,数据不会回滚。
MANDATORY 必须在一个已存在的事务 TxA 中执行;如果不存在事务,则抛出异常 (IllegalTransactionStateException)。 强制要求调用方提供事务上下文。 加入 TxA 抛出异常。 加入 TxA,TxA 会回滚。
REQUIRES_NEW 总是挂起当前事务 TxA(如果存在),并创建一个全新的独立事务 TxB 来执行。 确保方法在完全独立的新事务中运行,与外部事务互不干扰。 挂起 TxA,创建 TxB 创建新事务 TxB TxB 独立回滚,TxA 的状态(挂起后恢复)不受影响。
NOT_SUPPORTED 总是以非事务方式执行;如果当前存在事务 TxA,则挂起该事务。 确保方法不使用事务。通常用于不涉及数据库写入的辅助操作。 挂起 TxA,非事务执行。 非事务执行。 不会触发事务回滚,因为没有事务上下文。
NEVER 总是以非事务方式执行;如果当前存在事务 TxA,则抛出异常 (IllegalTransactionStateException)。 严格禁止在事务中运行。 抛出异常。 非事务执行。 不会触发事务回滚。
NESTED 如果当前存在事务 TxA,则在其中创建一个逻辑上的嵌套事务(保存点 Savepoint)。如果外部 TxA 回滚,嵌套事务也会回滚。如果嵌套事务回滚,外部 TxA 可以选择不回滚。 提供局部的、可独立回滚的子事务。(依赖 JDBC Savepoint 特性,通常需使用 DataSourceTransactionManager) TxA 中创建保存点 TxB 创建新事务 TxB(行为同 REQUIRED)。 TxB 回滚时,TxA 可以捕滚到保存点;TxA 回滚时,TxB 必定回滚。

⚠️ 事务回滚的关键点:

  1. 外部回滚 vs 内部回滚 (REQUIRED)
    • 如果使用 REQUIRED,内部方法抛出异常,会导致整个外部事务 (TxA) 被标记为 rollback-only,最终外部事务提交时会抛出 UnexpectedRollbackException
  2. 独立事务 (REQUIRES_NEW)
    • 使用 REQUIRES_NEW 时,如果内部事务 TxB 失败,只会回滚 TxB 的操作,而外部事务 TxA 会继续提交或回滚,两者完全隔离。
  3. 嵌套事务 (NESTED)
    • NESTED 创建的子事务依赖于父事务。子事务失败可以不影响父事务的提交(父事务可以捕滚到保存点),但父事务失败会强制子事务回滚。