/** * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. * <p>This is typically the default setting of a transaction definition. */ int PROPAGATION_REQUIRED = 0; /** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * PROPAGATION_SUPPORTS is slightly different from no transaction at all, * as it defines a transaction scopp that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ int PROPAGATION_SUPPORTS = 1; /** * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */ int PROPAGATION_MANDATORY = 2; /** * Create a new transaction, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work on out-of-the-box * on all transaction managers. This in particular applies to JtaTransactionManager, * which requires the <code>javax.transaction.TransactionManager</code> to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ int PROPAGATION_REQUIRES_NEW = 3; /** * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work on out-of-the-box * on all transaction managers. This in particular applies to JtaTransactionManager, * which requires the <code>javax.transaction.TransactionManager</code> to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ int PROPAGATION_NOT_SUPPORTED = 4; /** * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */ int PROPAGATION_NEVER = 5; /** * Execute within a nested transaction if a current transaction exists, * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. * <p>Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager when working on a JDBC 3.0 driver. * Some JTA providers might support nested transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ int PROPAGATION_NESTED = 6;
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 若是当前没有事务,就新建一个事务,若是已经存在一个事务中,加入到这个事务中。这是最多见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,若是当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,若是当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,若是当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,若是当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 若是当前存在事务,则在嵌套事务内执行。若是当前没有事务,则执行与PROPAGATION_REQUIRED相似的操做。 |
在我所见过的误解中, 最多见的是下面这种:java
假若有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现以下 :spring
/** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { // 调用 ServiceB 的方法 ServiceB.methodB(); }
那么若是 ServiceB 的 methodB 若是配置了事务, 认为就必须配置为 PROPAGATION_NESTED app
这种想法可能害了很多人, 认为 Service 之间应该避免互相调用, 其实根本不用担忧这点,PROPAGATION_REQUIRED 已经说得很明白。若是当前线程中已经存在事务, 方法调用会加入此事务, 若是当前没有事务,就新建一个事务, 因此 ServiceB#methodB() 的事务只要遵循最普通的规则配置为PROPAGATION_REQUIRED 便可, 若是 ServiceB#methodB (咱们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(咱们称之为外部事务) 若是没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是必定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 仍是 Dao 里 。
说了这么半天, 那到底什么是真正的事务嵌套呢, 解释以前咱们来看一下 Juergen Hoeller 的原话 。ide
Juergen Hoeller 写道:函数
PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.
Such independent inner transactions are for example used for id generation through manual sequences, where the access to the sequence table should happen in its own transactions, to keep the lock there as short as possible. The goal there is to avoid tying the sequence locks to the (potentially much longer running) outer transaction, with the sequence lock not getting released before completion of the outer transaction.
PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. íf the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction.
Nested transactions essentially allow to try some execution subpaths as subtransactions: rolling back to the state at the beginning of the failed subpath, continuing with another subpath or with the main execution path there - all within one isolated transaction, and not losing any previous work done within the outer transaction.
For example, consider parsing a very large input file consisting of account transfer blocks: The entire file should essentially be parsed within one transaction, with one single commit at the end. But if a block fails, its transfers need to be rolled back, writing a failure marker somewhere. You could either start over the entire transaction every time a block fails, remembering which blocks to skip - or you mark each block as a nested transaction, only rolling back that specific set of operations, keeping the previous work of the outer transaction. The latter is of course much more efficient, in particular when a block at the end of the file fails. ui
也就是说, 最容易弄混淆的实际上是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下。 this
Juergen Hoeller 的话:那么,外部事务如何利用嵌套事务的 savepoint 特性呢, 咱们用代码来讲话。spa
ServiceA { /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { ServiceB.methodB(); } } ServiceB { /** * 事务属性配置为 PROPAGATION_REQUIRES_NEW */ void methodB() { } }
这种状况下, 由于 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 因此二者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会由于对方的执行状况而影响事务的结果, 由于它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .
那么,PROPAGATION_NESTED 又是怎么回事呢? 继续看代码:.net
ServiceA { /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { ServiceB.methodB(); } } ServiceB { /** * 事务属性配置为 PROPAGATION_NESTED */ void methodB() { } }
如今的状况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时二者之间又将如何协做呢? 从 Juergen Hoeller 的原话中咱们能够找到答案, ServiceB#methodB 若是 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 能够有如下两种处理方式。prototype
1. 改写 ServiceA 以下
ServiceA { /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { try { ServiceB.methodB(); } catch (SomeException) { // 执行其余业务, 如 ServiceC.methodC(); } } }
这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 若是 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行以前的 SavePoint, 因此不会产生脏数据(至关于此方法从未执行过), 这种特性能够用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法作到这一点. (题外话 : 看到这种代码, 彷佛似曾相识, 想起了 prototype.js 中的 Try 函数 )
2. 代码不作任何修改, 那么若是内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行以前的 SavePoint(在任何状况下都会如此),
外部事务(即 ServiceA#methodA) 将根据具体的配置决定本身是 commit 仍是 rollback (+MyCheckedException)。
参考: