spring transaction PROPAGATION_NESTED与PROPAGATION_REQUIRES_NEW
spring事务中 传播机制包括request,request_new,nested等。
transactionStatus 状态有 包含savePoint,newTransaction,
当嵌套事务时,第一个事务属于newTransaction, 嵌套事务属于savePoint状态
当事务提交时,AbstracePlatformTransactionManager.processCommit中
1)嵌套事务 判断在savePoint状态,releaseHeldSavepoint释放savePoint,不提交事务,等第一个事务提交时一起提交
2)第一个事务 判断在newTransaction状态,doCommit,提交事务。
1 private void processCommit(DefaultTransactionStatus status) throws TransactionException { 2 try { 3 boolean beforeCompletionInvoked = false; 4 try { 5 prepareForCommit(status); 6 triggerBeforeCommit(status); 7 triggerBeforeCompletion(status); 8 beforeCompletionInvoked = true; 9 boolean globalRollbackOnly = false; 10 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { 11 globalRollbackOnly = status.isGlobalRollbackOnly(); 12 } 13 if (status.hasSavepoint()) {//嵌套事务 14 if (status.isDebug()) { 15 logger.debug("Releasing transaction savepoint"); 16 } 17 status.releaseHeldSavepoint(); 18 } 19 else if (status.isNewTransaction()) {//第一个事务 20 if (status.isDebug()) { 21 logger.debug("Initiating transaction commit"); 22 } 23 doCommit(status); 24 } 25 // Throw UnexpectedRollbackException if we have a global rollback-only 26 // marker but still didn't get a corresponding exception from commit. 27 if (globalRollbackOnly) { 28 throw new UnexpectedRollbackException( 29 "Transaction silently rolled back because it has been marked as rollback-only"); 30 } 31 } 32 catch (UnexpectedRollbackException ex) { 33 // can only be caused by doCommit 34 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 35 throw ex; 36 } 37 catch (TransactionException ex) { 38 // can only be caused by doCommit 39 if (isRollbackOnCommitFailure()) { 40 doRollbackOnCommitException(status, ex); 41 } 42 else { 43 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 44 } 45 throw ex; 46 } 47 catch (RuntimeException ex) { 48 if (!beforeCompletionInvoked) { 49 triggerBeforeCompletion(status); 50 } 51 doRollbackOnCommitException(status, ex); 52 throw ex; 53 } 54 catch (Error err) { 55 if (!beforeCompletionInvoked) { 56 triggerBeforeCompletion(status); 57 } 58 doRollbackOnCommitException(status, err); 59 throw err; 60 } 61 62 // Trigger afterCommit callbacks, with an exception thrown there 63 // propagated to callers but the transaction still considered as committed. 64 try { 65 triggerAfterCommit(status); 66 } 67 finally { 68 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); 69 } 70 71 } 72 finally { 73 cleanupAfterCompletion(status); 74 } 75 }
当回退时,AbstracePlatformTransactionManager.processRollback中
1)嵌套事务 判断在savePoint状态,rollbackToHeldSavepoint,rollback到savePoint点,并且释放savePoint;
2)第一个事务 判断在newTransaction状态,doRollback,rollback事务。
1 private void processRollback(DefaultTransactionStatus status) { 2 try { 3 try { 4 triggerBeforeCompletion(status); 5 if (status.hasSavepoint()) {//嵌套事务 6 if (status.isDebug()) { 7 logger.debug("Rolling back transaction to savepoint"); 8 } 9 status.rollbackToHeldSavepoint(); 10 } 11 else if (status.isNewTransaction()) {//第一个事务 12 if (status.isDebug()) { 13 logger.debug("Initiating transaction rollback"); 14 } 15 doRollback(status); 16 } 17 else if (status.hasTransaction()) {//request传播级别中第二个事务 18 if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { 19 if (status.isDebug()) { 20 logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); 21 } 22 doSetRollbackOnly(status); 23 } 24 else { 25 if (status.isDebug()) { 26 logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); 27 } 28 } 29 } 30 else { 31 logger.debug("Should roll back transaction but cannot - no transaction available"); 32 } 33 } 34 catch (RuntimeException ex) { 35 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 36 throw ex; 37 } 38 catch (Error err) { 39 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); 40 throw err; 41 } 42 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 43 } 44 finally { 45 cleanupAfterCompletion(status); 46 } 47 }
事务aop处理过程中的一个关键方法TransactionAspectSupport.invokeWithinTransaction()中
1 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) 2 throws Throwable { 3 4 // If the transaction attribute is null, the method is non-transactional. 5 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); 6 final PlatformTransactionManager tm = determineTransactionManager(txAttr); 7 final String joinpointIdentification = methodIdentification(method, targetClass); 8 9 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { 10 // Standard transaction demarcation with getTransaction and commit/rollback calls. 11 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); 12 Object retVal = null; 13 try { 14 // This is an around advice: Invoke the next interceptor in the chain. 15 // This will normally result in a target object being invoked. 最终调用目标方法 16 retVal = invocation.proceedWithInvocation(); 17 } 18 catch (Throwable ex) { 19 // target invocation exception 当目标方法运行异常,一 该事务rollback处理 二 抛出异常到调用方,如果调用方有事务,并且无try catch,调用方事务也会rollback 20 completeTransactionAfterThrowing(txInfo, ex); 21 throw ex; 22 } 23 finally { 24 cleanupTransactionInfo(txInfo); 25 } 26 commitTransactionAfterReturning(txInfo); 27 return retVal; 28 } 29 30 else { 31 // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. 32 try { 33 Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, 34 new TransactionCallback<Object>() { 35 @Override 36 public Object doInTransaction(TransactionStatus status) { 37 TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); 38 try { 39 return invocation.proceedWithInvocation(); 40 } 41 catch (Throwable ex) { 42 if (txAttr.rollbackOn(ex)) { 43 // A RuntimeException: will lead to a rollback. 44 if (ex instanceof RuntimeException) { 45 throw (RuntimeException) ex; 46 } 47 else { 48 throw new ThrowableHolderException(ex); 49 } 50 } 51 else { 52 // A normal return value: will lead to a commit. 53 return new ThrowableHolder(ex); 54 } 55 } 56 finally { 57 cleanupTransactionInfo(txInfo); 58 } 59 } 60 }); 61 62 // Check result: It might indicate a Throwable to rethrow. 63 if (result instanceof ThrowableHolder) { 64 throw ((ThrowableHolder) result).getThrowable(); 65 } 66 else { 67 return result; 68 } 69 } 70 catch (ThrowableHolderException ex) { 71 throw ex.getCause(); 72 } 73 } 74 }
demo代码 https://files.cnblogs.com/files/toUpdating/sluggarddd-spring-tx-demo-master.zip
最后,看一下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. if 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为给定范围启动一个新的独立“内部”事务。此事务将完全独立于外部事务提交或回滚,具有自己的隔离范围,自己的锁定等。外部事务将在内部事务开始时暂停,并在内部事务开始时恢复完成。
这种独立的内部事务例如用于通过手动序列生成id,其中对序列表的访问应该在其自己的事务中发生,以使锁定尽可能短。目标是避免将序列锁绑定到(可能更长时间运行)外部事务,并且在完成外部事务之前不会释放序列锁。
另一方面,PROPAGATION_NESTED启动“嵌套”事务,这是现有事务的真正子事务。会发生什么是在嵌套事务开始时将采取保存点。如果嵌套事务失败,我们将回滚到该保存点。嵌套事务是外部事务的一部分,因此它只会在外部事务的末尾提交。
嵌套事务本质上允许尝试一些执行子路径作为子事务:回滚到失败子路径开始时的状态,继续使用另一个子路径或主执行路径 - 所有这些都在一个隔离的事务中,并且不会丢失任何先前的工作在外部交易中。
例如,考虑解析由帐户传输块组成的非常大的输入文件:整个文件应基本上在一个事务中解析,最后一个提交。但是如果一个块失败,它的传输需要回滚,在某处写一个失败标记。您可以在每次块失败时重新开始整个事务,记住要跳过的块 - 或者将每个块标记为嵌套事务,只回滚该特定操作集,保留外部事务的先前工作。后者当然要高效得多,特别是当文件末尾的块失败时。
回滚整个事务是选择启动外部事务的分界代码/配置。
因此,如果内部事务抛出异常并且应该回滚(根据回滚规则),则事务将回滚到内部事务开始时采用的保存点。然后,立即调用代码可以决定捕获异常并继续执行外部事务中的其他路径。
如果调用内部事务的代码允许异常向上传播调用链,则异常最终将到达外部事务的分界代码。此时,外部事务的回滚规则决定是否触发回滚。那将是整个外部交易的回滚。
基本上,它取决于您的异常处理。如果捕获内部事务抛出的异常,则可以继续执行外部事务中的其他路径。如果让异常传播到调用链上,它最终会导致整个外部事务的回滚。