- 浏览: 240060 次
- 性别:
- 来自: 上海
最新评论
-
weigeshikebi:
不得不赞一个
解惑 spring 嵌套事务 -
siemens800:
Mac OS X 10.7.2 的光盘还有挖,帅锅帮刻个盘发来 ...
MacBook 升级内存记 -
cry615:
帖子很不错,java里任何一个东西都是一门学问,很有很强的逻辑 ...
理解 Java 的 GC 与 幽灵引用 -
sharkka:
<div class="quote_title ...
解惑 spring 嵌套事务 -
sogo1986:
楼主举的例子并没用体 ...
解惑 spring 嵌套事务
解惑 spring 嵌套事务
/**
* @author 王政
* @date 2006-11-24
* @note 转载请注明出处
*/
在所有使用 spring 的应用中, 声明式事务管理可能是使用率最高的功能了, 但是, 从我观察到的情况看,
绝大多数人并不能深刻理解事务声明中不同事务传播属性配置的的含义, 让我们来看一下 TransactionDefinition 接口中的定义
我们可以看到, 在 spring 中一共定义了六种事务传播属性, 如果你觉得看起来不够直观, 那么我来转贴一个满大街都有的翻译
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
在我所见过的误解中, 最常见的是下面这种:
假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
// 调用 ServiceB 的方法
ServiceB.methodB();
}
那么如果 ServiceB 的 methodB 如果配置了事务, 就必须配置为 PROPAGATION_NESTED
这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,
如果当前线程中已经存在事务, 方法调用会加入此事务, 果当前没有事务,就新建一个事务, 所以 ServiceB#methodB() 的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务) 如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?
说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下 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.
Rolling back the entire transaction is the choice of the demarcation code/config that started the outer transaction.
So if an inner transaction throws an exception and is supposed to be rolled back (according to the rollback rules), the transaction will get rolled back to the savepoint taken at the start of the inner transaction. The immediate calling code can then decide to catch the exception and proceed down some other path within the outer transaction.
If the code that called the inner transaction lets the exception propagate up the call chain, the exception will eventually reach the demarcation code of the outer transaction. At that point, the rollback rules of the outer transaction decide whether to trigger a rollback. That would be a rollback of the entire outer transaction then.
So essentially, it depends on your exception handling. If you catch the exception thrown by the inner transaction, you can proceed down some other path within the outer transaction. If you let the exception propagate up the call chain, it's eventually gonna cause a rollback of the entire outer transaction.
也就是说, 最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话
这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .
那么 PROPAGATION_NESTED 又是怎么回事呢? 继续看代码
现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
1. 改写 ServiceA 如下
这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 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).
上面大致讲述了潜套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager
一目了然
1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法
可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现
其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的子类 :
JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint :
2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了. (全文完)
不得不赞一个 楼主的说法很有道理
是的, 但是关键在于选择 PROPAGATION_REQUIRES_NEW 时 ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据, 而这些脏数据将可能导致后面的程序执行出错, 所以 SavePoint 是潜套事务的核心概念, 也是使用嵌套事务的必要前提
还是么有搞明白为什么上述情况会产生脏数据呢?期待Feiing解释一下!
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.
我知道怎么表述我的疑问了,就是requires_new 中的"inner" 事务的回滚对外面事务的影响. 如果是对同一个resource的操作(或者同时有别的resource),我在ServiceB.methodB中进行回滚,那么其回滚的范围也只在"inner"的事务之中的话,不也可以达到那么nested 事务所说的分支执行的效果?
Feiing 写道
是的, 但是关键在于选择 PROPAGATION_REQUIRES_NEW 时 ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据, 而这些脏数据将可能导致后面的程序执行出错, 所以 SavePoint 是潜套事务的核心概念, 也是使用嵌套事务的必要前提
Feiing 能不能就这个savepoint 举个情景,因为我也有这个疑问: ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据 ???怎样的情形产生脏数据?
/**
* @author 王政
* @date 2006-11-24
* @note 转载请注明出处
*/
在所有使用 spring 的应用中, 声明式事务管理可能是使用率最高的功能了, 但是, 从我观察到的情况看,
绝大多数人并不能深刻理解事务声明中不同事务传播属性配置的的含义, 让我们来看一下 TransactionDefinition 接口中的定义
/** * 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;
我们可以看到, 在 spring 中一共定义了六种事务传播属性, 如果你觉得看起来不够直观, 那么我来转贴一个满大街都有的翻译
引用
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
在我所见过的误解中, 最常见的是下面这种:
引用
假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
// 调用 ServiceB 的方法
ServiceB.methodB();
}
那么如果 ServiceB 的 methodB 如果配置了事务, 就必须配置为 PROPAGATION_NESTED
这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,
如果当前线程中已经存在事务, 方法调用会加入此事务, 果当前没有事务,就新建一个事务, 所以 ServiceB#methodB() 的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务) 如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?
说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下 Juergen Hoeller 的原话
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.
Juergen Hoeller 写道
Rolling back the entire transaction is the choice of the demarcation code/config that started the outer transaction.
So if an inner transaction throws an exception and is supposed to be rolled back (according to the rollback rules), the transaction will get rolled back to the savepoint taken at the start of the inner transaction. The immediate calling code can then decide to catch the exception and proceed down some other path within the outer transaction.
If the code that called the inner transaction lets the exception propagate up the call chain, the exception will eventually reach the demarcation code of the outer transaction. At that point, the rollback rules of the outer transaction decide whether to trigger a rollback. That would be a rollback of the entire outer transaction then.
So essentially, it depends on your exception handling. If you catch the exception thrown by the inner transaction, you can proceed down some other path within the outer transaction. If you let the exception propagate up the call chain, it's eventually gonna cause a rollback of the entire outer transaction.
也就是说, 最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话
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 又是怎么回事呢? 继续看代码
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) 可以有以下两种处理方式:
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).
上面大致讲述了潜套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager
/** * Create a TransactionStatus for an existing transaction. */ private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { ... 省略 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { if (!isNestedTransactionAllowed()) { throw new NestedTransactionNotSupportedException( "Transaction manager does not allow nested transactions by default - " + "specify 'nestedTransactionAllowed' property with value 'true'"); } if (debugEnabled) { logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); } if (useSavepointForNestedTransaction()) { // Create savepoint within existing Spring-managed transaction, // through the SavepointManager API implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. DefaultTransactionStatus status = newTransactionStatus(definition, transaction, false, false, debugEnabled, null); status.createAndHoldSavepoint(); return status; } else { // Nested transaction through nested begin and commit/rollback calls. // Usually only for JTA: Spring synchronization might get activated here // in case of a pre-existing JTA transaction. doBegin(transaction, definition); boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER); return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null); } } }
一目了然
1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法
/** * Create a savepoint and hold it for the transaction. * @throws org.springframework.transaction.NestedTransactionNotSupportedException * if the underlying transaction does not support savepoints */ public void createAndHoldSavepoint() throws TransactionException { setSavepoint(getSavepointManager().createSavepoint()); }
可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现
其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的子类 :
JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint :
2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了. (全文完)
评论
56 楼
weigeshikebi
2017-03-27
不得不赞一个
55 楼
sharkka
2011-11-07
sogo1986 写道
楼主举的例子并没用体现出Holler说的话的真实含义,导致楼下很多人问楼主,楼主却不知道如何回答,不过不可否认此贴确实为好贴。
大家仔细看看Holler最后的example就明白NESTED和REQUIED_NEW的真正区别在哪里:
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.
楼主举的例子中,PROPAGATION_REQUIRES_NEW恰好可以做到和PROPAGATION_NESTED同样的事情,这也难怪楼下疑惑。如果楼主的例子改为:
method A{
B.method B();
C.method C();
D.method D();
}
如果B,C,D方法都配置PROPAGATION_NESTED,那么3个方法都是属于外部事务A的子事务,如果B,C都正常执行,C出现异常,这个时候savepoint在D之前,也就是回滚到D执行之前,这个时候之前做的B和C的工作并没用白费,此时我们可以考虑使用分支执行其它后续处理方法保证业务完整性,最后是提交/回滚外部事务,根据你的逻辑和配置来定。
如果B,C,D方法都配置PROPAGATION_REQUIED_NEW,3个方法都创建自己独立的事务环境,和外部事务无任何关联。此时如果B,C正常执行,D出现异常,B,C的数据也已经提交,外部事务即使回滚,B,C数据也已经更新到数据库中产生脏数据
如果B,C,D方法都配置PROPAGATION_REQUIED,那么3个方法都加入到外部事务中,构成一个整体事务,任何一个方法出现异常,那么整个事务将会回滚。
大家仔细看看Holler最后的example就明白NESTED和REQUIED_NEW的真正区别在哪里:
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.
楼主举的例子中,PROPAGATION_REQUIRES_NEW恰好可以做到和PROPAGATION_NESTED同样的事情,这也难怪楼下疑惑。如果楼主的例子改为:
method A{
B.method B();
C.method C();
D.method D();
}
如果B,C,D方法都配置PROPAGATION_NESTED,那么3个方法都是属于外部事务A的子事务,如果B,C都正常执行,C出现异常,这个时候savepoint在D之前,也就是回滚到D执行之前,这个时候之前做的B和C的工作并没用白费,此时我们可以考虑使用分支执行其它后续处理方法保证业务完整性,最后是提交/回滚外部事务,根据你的逻辑和配置来定。
如果B,C,D方法都配置PROPAGATION_REQUIED_NEW,3个方法都创建自己独立的事务环境,和外部事务无任何关联。此时如果B,C正常执行,D出现异常,B,C的数据也已经提交,外部事务即使回滚,B,C数据也已经更新到数据库中产生脏数据
如果B,C,D方法都配置PROPAGATION_REQUIED,那么3个方法都加入到外部事务中,构成一个整体事务,任何一个方法出现异常,那么整个事务将会回滚。
不得不赞一个 楼主的说法很有道理
54 楼
sogo1986
2011-11-07
楼主举的例子并没用体现出Holler说的话的真实含义,导致楼下很多人问楼主,楼主却不知道如何回答,不过不可否认此贴确实为好贴。
大家仔细看看Holler最后的example就明白NESTED和REQUIED_NEW的真正区别在哪里:
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.
楼主举的例子中,PROPAGATION_REQUIRES_NEW恰好可以做到和PROPAGATION_NESTED同样的事情,这也难怪楼下疑惑。如果楼主的例子改为:
method A{
B.method B();
C.method C();
D.method D();
}
如果B,C,D方法都配置PROPAGATION_NESTED,那么3个方法都是属于外部事务A的子事务,如果B,C都正常执行,C出现异常,这个时候savepoint在D之前,也就是回滚到D执行之前,这个时候之前做的B和C的工作并没用白费,此时我们可以考虑使用分支执行其它后续处理方法保证业务完整性,最后是提交/回滚外部事务,根据你的逻辑和配置来定。
如果B,C,D方法都配置PROPAGATION_REQUIED_NEW,3个方法都创建自己独立的事务环境,和外部事务无任何关联。此时如果B,C正常执行,D出现异常,B,C的数据也已经提交,外部事务即使回滚,B,C数据也已经更新到数据库中产生脏数据
如果B,C,D方法都配置PROPAGATION_REQUIED,那么3个方法都加入到外部事务中,构成一个整体事务,任何一个方法出现异常,那么整个事务将会回滚。
大家仔细看看Holler最后的example就明白NESTED和REQUIED_NEW的真正区别在哪里:
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.
楼主举的例子中,PROPAGATION_REQUIRES_NEW恰好可以做到和PROPAGATION_NESTED同样的事情,这也难怪楼下疑惑。如果楼主的例子改为:
method A{
B.method B();
C.method C();
D.method D();
}
如果B,C,D方法都配置PROPAGATION_NESTED,那么3个方法都是属于外部事务A的子事务,如果B,C都正常执行,C出现异常,这个时候savepoint在D之前,也就是回滚到D执行之前,这个时候之前做的B和C的工作并没用白费,此时我们可以考虑使用分支执行其它后续处理方法保证业务完整性,最后是提交/回滚外部事务,根据你的逻辑和配置来定。
如果B,C,D方法都配置PROPAGATION_REQUIED_NEW,3个方法都创建自己独立的事务环境,和外部事务无任何关联。此时如果B,C正常执行,D出现异常,B,C的数据也已经提交,外部事务即使回滚,B,C数据也已经更新到数据库中产生脏数据
如果B,C,D方法都配置PROPAGATION_REQUIED,那么3个方法都加入到外部事务中,构成一个整体事务,任何一个方法出现异常,那么整个事务将会回滚。
53 楼
wst0350
2007-09-11
处于学习阶段,
看不大懂,
如果能一直看到这样的帖子,那就太好了!
看不大懂,
如果能一直看到这样的帖子,那就太好了!
52 楼
magice
2007-05-28
Feiing 写道
是的, 但是关键在于选择 PROPAGATION_REQUIRES_NEW 时 ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据, 而这些脏数据将可能导致后面的程序执行出错, 所以 SavePoint 是潜套事务的核心概念, 也是使用嵌套事务的必要前提
还是么有搞明白为什么上述情况会产生脏数据呢?期待Feiing解释一下!
51 楼
tap2008
2007-05-27
太棒了,今天终于写了几个测试,搞通了!谢谢!
50 楼
fuwang
2007-05-14
好像绝大部分人都不懂spring的嵌套事务, 可是大家还是照样用spring,这样做的项目可靠吗?
49 楼
hyhongyong
2007-04-12
真是好文,看了才明白为什么EJB中的事务配置是6种,而spring的是7种。
EJB3.0也应该加上这第7种啊
EJB3.0也应该加上这第7种啊
48 楼
v38
2007-04-11
很棒的分析,以前对事务的传播一直不明白,要谢谢楼主
47 楼
wensky222
2007-04-09
有点疑问没弄明白,就是嵌套事务回退是怎么处理的?
是由数据库自动实现不需要自己写代码了吧?
在sqlserver2000中:ROLLBACK TRAN语句总是属于最外层的事务,并且因此总是回滚整个事务而不论其中打开了多少嵌套事务
是由数据库自动实现不需要自己写代码了吧?
在sqlserver2000中:ROLLBACK TRAN语句总是属于最外层的事务,并且因此总是回滚整个事务而不论其中打开了多少嵌套事务
46 楼
andyandyandy
2007-03-28
好文,看了lz和跟贴的讨论,终于理解了
45 楼
flyfoxs
2007-03-28
分析的太棒了。
44 楼
JAVA_ED
2007-02-12
MS我需要第8种配置
doB是很消耗时间的操作
如果我需要在执行完XXXXX以后就commit(减少锁表时间)呢?
Service A{ doA(){ xxxxxxx; b.doB(); } }
doB是很消耗时间的操作
如果我需要在执行完XXXXX以后就commit(减少锁表时间)呢?
43 楼
javadev
2007-02-11
非常好,加入收藏。
42 楼
pikachu
2007-02-11
我的理解,
nested 可以在外层rollback所有内层的事务。
requiresnew 不行。
nested 可以在外层rollback所有内层的事务。
requiresnew 不行。
41 楼
OliverSegal
2007-01-30
对于楼主说的:‘选择 PROPAGATION_REQUIRES_NEW 时 ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据, 而这些脏数据将可能导致后面的程序执行出错’。有疑问,用PROPAGATION_REQUIRES_NEW时在"inner"事务中回滚怎么会产生脏数据呢?它也是回滚到方法执行以前呀!我感觉只有在"inner"事务完成了也就是commit过以后,外层事务又rollback掉了才会可能产生所谓的脏数据。
40 楼
coolfish
2007-01-21
引用
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.
引用
它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点.
我知道怎么表述我的疑问了,就是requires_new 中的"inner" 事务的回滚对外面事务的影响. 如果是对同一个resource的操作(或者同时有别的resource),我在ServiceB.methodB中进行回滚,那么其回滚的范围也只在"inner"的事务之中的话,不也可以达到那么nested 事务所说的分支执行的效果?
39 楼
coolfish
2007-01-21
引用
Feiing 写道
是的, 但是关键在于选择 PROPAGATION_REQUIRES_NEW 时 ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据, 而这些脏数据将可能导致后面的程序执行出错, 所以 SavePoint 是潜套事务的核心概念, 也是使用嵌套事务的必要前提
Feiing 能不能就这个savepoint 举个情景,因为我也有这个疑问: ServiceB.methodB 没办法回滚到它执行之前的 SavePoint, 这时已经产生了一些脏数据 ???怎样的情形产生脏数据?
38 楼
yhc0125
2006-12-28
经典好文,以前在项目中使用过PROPAGATION_NEW,看了楼主的文章,对PROPAGATION_NESTED有了比较明确的认识。
37 楼
hf200012
2006-12-26
经典,分析的很好
发表评论
-
Bookmarks
2010-07-07 16:42 1526Architecture J2EE cluster htt ... -
XML validation error on request: cvc-complex-type
2010-04-15 21:45 1375see http://72.5.124.102/threa ... -
Spring LoadTimeWeaver 的那些事儿
2009-10-05 00:39 4079DDD 现在越来越流行了, 不管正确与否, new U ... -
理解 Java 的 GC 与 幽灵引用
2009-06-04 03:02 3286理解 Java 的 GC 与 幽灵引用 J ... -
基于 Apache Mina 的 RPC 实现 (长连接 webservice)
2008-11-27 23:26 4390写了一个基于 Apache Mina 和 SpringRemo ... -
Atomikos JTA for Hibernate3
2007-11-22 14:52 3364http://wiki.atomikos.org/bin/vi ... -
Spring AOP 概览与细节
2007-08-05 03:21 6031@王政 @2007-08-04 @转载请注明 ... -
使用 FactoryBean 让你的 spring 配置动起来
2006-11-01 17:43 10492看到不少朋友讨论 spring 配置时认为 spring 配置 ... -
一个可能的列级权限控制方案讨论
2006-05-25 18:45 18011最近的项目需要做到列级权限控制, 大意如下 publi ... -
Spring 事务简化配置
2006-03-21 00:33 36794在 spring 中, 事务管理一般是通过声明一个 txPr ... -
再论 Acegi 权限存储策略
2006-02-18 00:17 12864本文原出处 http://starcraft.blogdriv ... -
以前写的一篇介绍 Acegi 的文档
2006-01-05 09:51 13297半年前写的, 版本是 0.8.3, 主要是翻译了一些 ref ... -
Acegi 资源配置动态扩展实现
2005-12-13 16:21 19198本文原出处 : http://starcr ...
相关推荐
Spring 事务配置解惑.html 抓下来打包成了HTML文件, 方便离线观看
讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。
java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑
3. 子查询:子查询是嵌套在其他SQL语句中的查询,可以用来检索满足特定条件的数据。书中可能通过实例演示如何使用子查询来解决复杂查询问题。 4. 聚合函数:如SUM、AVG、MAX、MIN和COUNT等,用于对一组值进行计算。...
《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...
总结起来,SQL解惑解惑涵盖了从基础语法到高级特性的全面知识,包括查询、更新、数据管理、性能优化、事务处理、数据库设计和版本差异等多个方面。通过深入理解和实践,我们可以成为SQL的熟练驾驭者,解决各种数据库...
14. **Spring框架**:Spring是Java开发中最流行的应用框架,包括依赖注入、AOP(面向切面编程)、MVC等模块,广泛应用于企业级应用开发。 15. **并发编程**:Java并发库提供了丰富的工具,如ExecutorService、...
IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书
《IT学生解惑真经》是一本专门为在IT领域学习和探索的学生们量身打造的知识宝典。这本书的目的是帮助那些在信息技术世界中迷失方向、渴望深入理解和掌握核心技术的学子们,提供一套全面且实用的学习指南。书中的内容...
《Java解惑中文版》是一本专为Java程序员设计的指南,旨在帮助读者解决在编程过程中遇到的各种问题,提升程序的健壮性。本书深入浅出地探讨了Java语言的核心概念、常见疑惑以及最佳实践,旨在使开发者能够编写出更...
"java解惑" PDF版本
《IT解惑》是一部综合性的资源集合,包含了《IT学生解惑真经》、《程序员羊皮卷》和《高质量C编程指南》三部分,旨在为计算机科学与技术的学习者和未来的职业程序员提供全面的指导和建议。这些文档分别关注了IT学生...
以上只是Java编程中的一部分知识点,实际上Java还有许多其他领域,如Spring框架、JDBC数据库操作、JPA实体映射、Maven构建工具、单元测试等。"Java解惑"这本书或者资源可能包含了这些内容的详细解答,通过学习和实践...
文档《java解惑 PDF版》中列举了95个这样的谜题,每个谜题都旨在帮助开发者理解并纠正一些常见的错误理解。以下是根据提供的部分内容解析的几个相关知识点。 ### 表达式谜题与取余操作符(%)的行为 在Java中,...
《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版
《C语言解惑》是一本针对C语言编程学习者的进阶书籍,旨在帮助读者从错误分析的角度提高编程技能和代码质量。本书不仅适合编程初学者,也可以作为具有一定基础的编程者提升实用编程能力的参考资料。书籍内容涵盖了...
### C语言解惑知识点概述 #### 一、文档简介 《C语言解惑》是一本旨在帮助初学者和进阶者解决C语言编程过程中遇到的各种问题的书籍。本书通过丰富的实例和深入浅出的讲解,让读者能够快速掌握C语言的核心概念和技术...
7. **枚举与注解**:枚举类型提供了一种安全的常量表示方式,而注解可以为代码添加元数据,用于编译时或运行时的代码处理,如Spring框架中的依赖注入。 8. **设计模式**:Java中常见的设计模式如单例、工厂、观察者...