事务传播
本节描述了Spring中事务传播的一些语义。请注意,本节并不是对事务传播的正式介绍。相反,它详细说明了Spring中关于事务传播的一些语义。
在Spring管理的事务中,需要注意物理交易和逻辑交易之间的区别,以及传播设置(propagation setting)是如何影响这一区别的。
理解 PROPAGATION_REQUIRED

PROPAGATION_REQUIRED 强制执行物理事务:如果当前作用域内尚不存在事务,则在本地执行;如果存在为更大作用域定义的“外部”事务,则参与该事务。在同一线程内的常见调用栈结构中,这是一个合理的默认设置(例如,服务外观层将请求委托给多个仓库方法,而这些方法所涉及的所有底层资源都必须参与服务级事务)。
默认情况下,参与交易的会继承外部作用域的属性,默默忽略本地隔离级别、超时值或只读标志(如果有的话)。如果你希望在参与与其他隔离级别不同的现有交易时,让隔离级别的声明被拒绝,可以考虑将事务管理器中的validateExistingTransactions标志设置为true。这种较为严格的模式还会拒绝只读不匹配的情况(也就是说,一个读写事务试图参与只读的外部作用域)。
当传播设置为 PROPAGATION_REQUIRED 时,对于每个应用了该设置的方法,都会创建一个逻辑事务范围(logical transaction scope)。每个这样的逻辑事务范围都可以单独确定“仅回滚”(rollback-only)状态,且外部事务范围(outer transaction scope)与内部事务范围(inner transaction scope)在逻辑上是相互独立的。在标准的 PROPAGATION_REQUIRED 行为中,所有这些事务范围都会映射到同一个物理事务(physical transaction)上。因此,在内部事务范围内设置的“仅回滚”标志确实会影响外部事务是否能够成功提交(commit)。
然而,在内层事务范围设置了“仅回滚”标志的情况下,外层事务本身尚未决定是否进行回滚,因此由内层事务范围触发的回滚是意外的。此时会抛出一个相应的UnexpectedRollbackException异常。这种行为是预料之中的,目的是防止事务调用者误以为已经执行了提交操作,而实际上并没有。所以,如果一个内层事务(外层调用者并不知情)悄悄地将该事务标记为“仅回滚”状态,外层调用者仍然会调用提交方法。此时,外层调用者需要收到UnexpectedRollbackException异常,才能明确知道实际上执行的是回滚操作。
理解PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW与PROPAGATION_REQUIRED不同,它总是为每个受影响的交易范围使用独立的物理事务,从不参与外部范围的现有事务。在这种安排下,底层的资源事务是独立的,因此可以独立地提交或回滚;外部事务不会受到内部事务回滚状态的影响,而内部事务在其完成后会立即释放锁。这样的独立内部事务还可以声明自己的隔离级别、超时设置和只读属性,而不会继承外部事务的特性。
当内部事务获取自己的资源(如新的数据库连接)时,绑定在外部事务上的资源将仍然保留在那里。如果有多个线程都有活跃的外部事务,并且都等待为它们的内部事务获取新的连接,而连接池又无法再分配任何这样的内部连接,这可能会导致连接池耗尽,甚至可能引发死锁。除非你的连接池大小适当(至少比并发线程的数量多1),否则不要使用PROPAGATION_REQUIRES_NEW。
理解 PROPAGATION_NESTED
PROPAGATION_NESTED 使用单个物理事务,并包含多个可以回滚的保存点(savepoint)。这种部分回滚机制允许内部事务范围触发其自身的回滚,而尽管有些操作已被回滚,外部事务仍可继续执行该物理事务。此设置通常与 JDBC 保存点相关联,因此它仅适用于 JDBC 资源事务。详情请参阅 Spring 的 DataSourceTransactionManager。