Above( Tangent Analysis of Spring Transactions ) In this article, we explain how Spring determines whether the target method needs to weave into aspect logic, which explains that transaction logic is woven through Transaction Interceptor. This article mainly explains how Transaction Interceptor weaves into aspect logic.
1. Global Transaction Logic
Spring implements aspect logic weaving through Aop, where the Transaction Interceptor implements the Method Interceptor interface, which inherits the Advice interface, that is to say, the Transaction Interceptor is essentially only part of the aspect logic that Spring Aop needs to weave into. The following is the source code for the invoke() method implemented by Transaction Interceptor:
@Override @Nullable public Object invoke(final MethodInvocation invocation) throws Throwable { // Get the target class that needs to be woven into transaction logic Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Weaving transaction logic return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
Here the weaving of transaction logic is encapsulated in invokeWithin Transaction (), and we continue to read its source code:
@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // Get the relevant attributes in @Transactional TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); // Get the current Transaction Manager configuration, which is typically configured in the configuration file final PlatformTransactionManager tm = determineTransactionManager(txAttr); // Get a signature of the current method final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); // If the configured Transaction Manager is not of the Callback Preferring Platform Transaction Manager type, // Create a new transaction for the execution of the current method if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Create a new transaction for the execution of the current method TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // Target implementation approach retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // Handling exceptions while executing throw exceptions and weaving exception handling logic completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { // Execute the logic of transaction completion, whether the transaction needs to be committed or rolled back cleanupTransactionInfo(txInfo); } // Submit the current transaction commitTransactionAfterReturning(txInfo); return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); try { // If the current Transaction Manager implements Callback Preferring Platform Transaction Manager, // The transaction is processed through its execute() method. Here is Callback Preferring Platform- // The role of Transaction Manager is that it provides an execute() method for providing customization // Transaction Manager class implements transaction-related processing logic Object result = ((CallbackPreferringPlatformTransactionManager) tm) .execute(txAttr, status -> { // Get the Transaction configuration TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { // Call the target method return invocation.proceedWithInvocation(); } catch (Throwable ex) { // If the current exception needs to be rolled back, the exception is rolled back and thrown if (txAttr.rollbackOn(ex)) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { throwableHolder.throwable = ex; return null; } } finally { // Clear saved Transaction information cleanupTransactionInfo(txInfo); } }); // If an exception is executed, the exception is thrown if (throwableHolder.throwable != null) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); } throw ex2; } } }
As you can see, the backbone logic weaved by Spring transaction logic is in the invokeWithinTransaction() method, which first determines whether a transaction needs to be created and encapsulates it as a TransactionInfo object, regardless of whether it exists or not. Then the target method is invoked. If the target method performs error reporting, the operation when exception is thrown is performed, which mainly includes transaction rollback and exception callback logic. The current transaction attributes are then cleaned up. If the current transaction is a new transaction, it commits directly. If a transaction uses a savepoint, it releases the savepoint it holds and commits the transaction.
2. Create a transaction
When weaving transaction logic, the first thing to do is create transactions, and Spring does not create transactions at will. It determines what types of transactions need to be created by the configuration attributes of transactions. Transaction creation is mainly in the createTransactionIfNecessary() method. The source code of this method is as follows:
protected TransactionInfo createTransactionIfNecessary( @Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { // If the name of the TransactionAttribute is empty, a TransactionAttribute of the agent is created. // And set its name to the name of the method that needs to be woven into the transaction if (txAttr != null && txAttr.getName() == null) { txAttr = new DelegatingTransactionAttribute(txAttr) { @Override public String getName() { return joinpointIdentification; } }; } TransactionStatus status = null; if (txAttr != null) { if (tm != null) { // If the transaction attribute is not empty and Transaction Manager exists, // Obtain the object of the current transaction status through Transaction Manager status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured"); } } } // Encapsulating the current transaction attributes and state as a TransactionInfo, the main task here is to bind the transaction attributes to the current thread return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }
For transaction creation, the first step is to determine whether the object name encapsulating transaction attributes is empty, if not, to use the identifier of the target method as its name, and then to create a transaction through Transaction Manager. The following is the source code for the TransactionManager.getTransaction() method:
@Override public final TransactionStatus getTransaction( @Nullable TransactionDefinition definition) throws TransactionException { // For Spring's BasicDataSource, this is to create a DataSourceTransactionObject object. // Its return value is different according to the specific implementation class, for example, hibernate returns a Hibernate Transaction Object object. // Jpa returns a JpaTransactionObject object Object transaction = doGetTransaction(); boolean debugEnabled = logger.isDebugEnabled(); // If Transaction Definition is empty, create a default Transaction Definition if (definition == null) { definition = new DefaultTransactionDefinition(); } // The way to determine whether the current method call is already in a transaction is to determine whether the current ConnectionHolder is empty. // And whether the existing transaction is in active state or not, if so, it indicates that the current transaction exists. If there is no business here, generally, // Its Connection Holder is worthless if (isExistingTransaction(transaction)) { // Determine whether the transactional propagability of the current method supports the existing transactional attributes, and return the encapsulated transactional attributes return handleExistingTransaction(definition, transaction, debugEnabled); } // Here the default value of timeout is - 1, and the expiration time set by the user cannot be less than - 1. if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); } // If the execution of the current method is not in a transaction and the transaction propagability of the current method is marked PROPAGATION_MANDATORY, // Because this propaganda requires that the current method execution must be in a transaction, and that the transaction must exist, there does not exist, and so on. // throw if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { throw new IllegalTransactionStateException("No existing transaction" + " found for transaction marked with propagation 'mandatory'"); } else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { // Determine whether the transaction propagability of the current method is one of REQUIRED, REQUIRES_NEW or NESTED. // Since the current method comes here to show that there is no transaction, a new transaction needs to be created for it. Here the suspend() method // When calling, a null is passed in. If the user sets the properties of the transaction event callbacks, they will be suspended temporarily. // And encapsulated in Suspended Resources Holder, if no callback event is registered, the method will return null SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); } try { // Determine whether the user has set properties that never execute transaction callback events boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); // Encapsulate the definition of current transaction attributes, existing transactions and pending transaction callback events into one // DefaultTransactionStatus object DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // Start a transaction by calling Jdbc's api doBegin(transaction, definition); // Set some attributes of the current transaction, such as isolation level, read-only or not, to the ThreadLocal variable for caching prepareSynchronization(status, definition); return status; } catch (RuntimeException | Error ex) { // If an exception is thrown by the current transaction, the pending transaction and transaction events are reloaded, because there are no transactions currently. // So the incoming transaction that needs to be loaded is null resume(null, suspendedResources); throw ex; } } else { if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { logger.warn("Custom isolation level specified but no actual transaction " + "initiated; isolation level will effectively be ignored: " + definition); } // This step shows that transactions are SUPPORTS, NOT_SUPPORTED or NEVER because there are no transactions at present. // For these kinds of propaganda, they do not need transactions, so there is no need to do other processing, directly encapsulating an empty transaction. // Transaction Statues is OK boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); } }
Here the getTransaction() method can be divided into two main aspects: 1) the current method call already exists in a transaction; 2) there is no transaction before the current method call. For the first point, we will talk about below, and for the second point, there are three main branches of judgment: 1) if the transaction propagation is MANDATORY, then throw an exception, because it requires that there must be a transaction; 2) if the transaction propagation is REQUIRED, REQUIRES_NEW or NESTED, then create a new transaction for its use; 3) For other cases, namely SUPPORTS, NOT_SU. PPORTED and NEVER, because there is no transaction at present, can directly create a TransactionStatus with empty transaction to return. For the processing of existing transactions, it is mainly carried out in the handleExistingTransaction() method. The source code of this method is as follows:
private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { // Going to this point indicates that a transaction already exists and throws an exception if the execution of the current method does not support an existing transaction if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } // For the propagability of the NOT_SUPPORTED type, if a transaction already exists, the transaction is suspended and the current method is guaranteed. // Execution is non-transactional; if there is no current transaction, it is not processed if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { if (debugEnabled) { logger.debug("Suspending current transaction"); } // Suspend an existing transaction Object suspendedResources = suspend(transaction); // Determine whether the user has set a callback function to always execute transaction events boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); // Encapsulating transaction results, where the incoming transaction is null because the current method requires no execution in the transaction, and for // Suspended transactions, which are subsequently reloaded, are passed in the suspended resources transaction attribute return prepareTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); } // If the propagation of the current transaction is REQUIRES_NEW, the current transaction is suspended, which requires that the current method must be in a new transaction. // Do it, so a new transaction will be created here, and then the transaction will be opened if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { if (debugEnabled) { logger.debug("Suspending current transaction, creating new transaction " + " with name [" + definition.getName() + "]"); } // Suspend the current transaction and transaction event callback function SuspendedResourcesHolder suspendedResources = suspend(transaction); try { // Determine whether the user has set a callback function that never executes transaction events boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); // Create a new transaction execution DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // Start a new business doBegin(transaction, definition); // Set properties such as isolation of new transactions, whether read-only or not to ThreadLocal prepareSynchronization(status, definition); return status; } catch (RuntimeException | Error beginEx) { // If an exception is thrown in a new transaction, the existing transaction is reloaded and executed resumeAfterBeginException(transaction, suspendedResources, beginEx); throw beginEx; } } // Determine whether the current transaction propaganda is NESTED, if so, and then determine whether the current data source supports nested transactions, if not, // Exceptions are thrown; if the data source specifies that nested transactions are executed using savepoints, nested transactions are simulated using savepoints. // If not specified, nested transactions are executed in a user-defined manner if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { // If nested transactions are not supported, exceptions are thrown 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 nested transactions are specified using savepoints, a savepoint is created to execute the current method. if (useSavepointForNestedTransaction()) { DefaultTransactionStatus status = prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); // Create savepoints status.createAndHoldSavepoint(); return status; } else { // If you do not specify a way to use savepoints, it is up to the user to use their own custom way, where only a new transaction will be opened. boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, null); doBegin(transaction, definition); prepareSynchronization(status, definition); return status; } } if (debugEnabled) { logger.debug("Participating in existing transaction"); } // At this point, the transaction propaganda is SUPPORTS or REQUIRED, both of which can only inherit existing transactions. // Execute the current method. Therefore, when inheriting, we will judge whether the transaction attribute of the existing method is in conflict with the transaction attribute of the current method. There // Judgment mainly includes two aspects: transaction isolation level and whether it is read-only or not. If the transaction isolation level and read-only attributes of the current method are integrated with the transaction // If inconsistent, an exception is thrown if (isValidateExistingTransaction()) { if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); // Determine whether the transaction isolation level is consistent or inconsistent and throw an exception if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { Constants isoConstants = DefaultTransactionDefinition.constants; throw new IllegalTransactionStateException("Participating transaction " + " with definition [" + definition + "] specifies isolation " + " level which is incompatible with existing transaction: " + (currentIsolationLevel != null ? isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : "(unknown)")); } } if (!definition.isReadOnly()) { // Determine whether read-only attributes are consistent, and if not, throw an exception if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { throw new IllegalTransactionStateException("Participating transaction " + " with definition [" + definition + "] is not marked as " + " read-only but existing transaction is"); } } } // Since both SUPPORTS and REQUIRED inherit the parent transaction to execute the current method, this is just a case of encapsulating the parent transaction and returning it. boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); }
The handleExistingTransaction() method here is mainly to determine whether a new transaction is needed in the case of an existing transaction. The basis of judgment here is mainly based on the transactional propaganda of the current method: (1) if the propaganda is NEVER, an exception will be thrown directly; (2) if the propaganda is NOT_SUPPORTED, the current transaction will be suspended and the current method will be executed in a non-transactional state; (3) if REQUIRES_NEW, the current transaction will be suspended and a new transaction will be created to execute the current method. If it is a nested transaction, it will determine whether the current data source supports the nested transaction and whether it needs to execute the nested transaction in the way of savepoints. _If it is SUPORTS or REQUIRED, it will directly reuse the current transaction execution target method.
3. Open a transaction
As far as transaction initiation is concerned, the doBegin() method here is concerned, where the main task of doBegin() is to open a database Connection and set autoCommit, isolation and readOnly attributes. It should be noted that the doBegin() method is a template method, and different Transaction Managers have different implementations. This paper mainly explains the implementation principle of DataSource Transaction Manager. The following is the source code for the doBegin() method:
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { // For DataSourceTransaction Manager, the transaction object is the DataSourceTransaction Object type. // So it can be forced directly here. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { // For the first open transaction, there is no Connection Holder, so the if logic will follow here. if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { // Getting a database Connection through the DataSource object Connection newCon = obtainDataSource().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } // Create a new Connection Holder, where the second parameter indicates whether or not the new Connection Holder is created txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } // Setting Synchronization State txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); // The main work of the prepareConnectionForTransaction() method here is to read readOnly and // isolation property and set it to ConnectionHolder if it already exists before // The isolation value, which is not the same as in definition, is set to previous Isolation Level Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); // Set autoCommit to false to control transaction submission if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } // Here, the readOnly attribute is set from the Statement level. prepareTransactionalConnection(con, definition); txObject.getConnectionHolder().setTransactionActive(true); // Determine whether the timeout attribute in definition has a valid value, and set it to ConnectionHolder if it exists int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // If a new Connection Holder is created, it is bound to the ThreadLocal object if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } } catch (Throwable ex) { // If an exception is thrown during the above execution and a new Connection Holder is created, the Connection is released if (txObject.isNewConnectionHolder()) { DataSourceUtils.releaseConnection(con, obtainDataSource()); txObject.setConnectionHolder(null, false); } throw new CannotCreateTransactionException("Could not open JDBC Connection " + " for transaction", ex); } }
4. exception handling
For exception handling, Spring transactions can be divided into two situations: first, if the current exception can be omitted, it will be submitted directly; and second, if the current exception cannot be omitted, it will be rolled back. Specific implementation source code in the completeTransaction AfterThrowing () method, the following is the source code of the method:
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { // Processing if transaction information exists and the transaction state is not empty if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } // If the current transaction configuration requires a rollback of the current exception type, it is rolled back. if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { // Here, when rolling back, we mainly use the Connection object to roll back. // In addition, transaction event functions are also invoked when rollback occurs. txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception",ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by rollback exception",ex); throw ex2; } } else { try { // If the current exception is not a type that needs to be rolled back, it does not roll back and commits the current transaction directly. txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by commit exception", ex); throw ex2; } } } }
As you can see, the rollback operation here is mainly in the TransactionManager.rollback() method, the source code of which is as follows:
@Override public final void rollback(TransactionStatus status) throws TransactionException { // If the current transaction has been completed, an exception is thrown if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback" + " more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; // Transaction rollback processRollback(defStatus, false); } private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { boolean unexpectedRollback = unexpected; try { // This triggers the before completion time, and it's important to note that // Only when the transaction state is a newly created transaction event synchronizer will it be triggered triggerBeforeCompletion(status); // If the current transaction is a savepoint-type transaction, rollback to the savepoint if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { // If the current transaction is a new transaction, rollback will occur at this time if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } doRollback(status); } else { // If it is not for the above two cases, it means that the current transaction is in a large transaction, at which time the rollback state will be set. // Exceptions are thrown directly by external calls if (status.hasTransaction()) { // If only partial rollback is set, or global exception rollback is set, rollback status is set. if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug("Participating transaction failed -" + " marking existing transaction as rollback-only"); } doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug("Participating transaction failed - " + "letting transaction originator decide on rollback"); } } } else { logger.debug("Should roll back transaction but cannot - " + "no transaction available"); } if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } catch (RuntimeException | Error ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } // Trigger the after completion event, where only the newly created transaction event synchronizer will trigger triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } } finally { // Clean up the transaction state saved in the current thread and reload the transaction if there is a pending transaction outside the current transaction cleanupAfterCompletion(status); } }
For the rollback of transactions, it can be seen that only the outermost transaction (which will be marked as a new transaction) will roll back, while the savepoint transaction, or internal transaction, will not roll back, but only rollback to the savepoint or mark the rollback status.
5. Submission
For transaction submission, it is mainly in the commitTransaction AfterReturning () method, in which Spring first determines whether the transaction has set up a global rollback, or if a local rollback is required due to an exception, then it implements a rollback-related strategy; if no rollback is required, it determines whether the transaction is a savepoint transaction. If so, release the current savepoint, if not, determine whether the current transaction is the outermost transaction, and if so, commit action will take place. In addition, before completion and after completion events are triggered before and after transaction submission, respectively. The following is the source code for the commitTransaction AfterReturning () method:
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } // If the transaction information and state are not empty, the transaction commit policy is executed txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } }
@Override public final void commit(TransactionStatus status) throws TransactionException { // If the current transaction is not completed, an exception is thrown if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback" + " more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; // If the current transaction is set to require local rollback due to exceptions, rollback occurs if (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); } processRollback(defStatus, false); return; } // If the current transaction sets up a global transaction that needs to be rolled back, the transaction rollback is performed if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Global transaction is marked as rollback-only but" + " transactional code requested commit"); } processRollback(defStatus, true); return; } // Execute Transaction Submission Order processCommit(defStatus); } private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { // Mark the completion event before the master triggers completion boolean beforeCompletionInvoked = false; try { boolean unexpectedRollback = false; // Prepare transaction submission related operations, here is just a hook method for providing custom transactions to subclasses prepareForCommit(status); // Triggering the before commit event triggerBeforeCommit(status); // Trigger the before completion event triggerBeforeCompletion(status); // Identify whether the before completion event has been invoked beforeCompletionInvoked = true; // If the current transaction is a savepoint transaction, the current savepoint is released to allow the outer transaction to continue executing if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Releasing transaction savepoint"); } unexpectedRollback = status.isGlobalRollbackOnly(); status.releaseHeldSavepoint(); } else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } // If the current transaction is an outermost transaction, the transaction is committed unexpectedRollback = status.isGlobalRollbackOnly(); doCommit(status); } else if (isFailEarlyOnGlobalRollbackOnly()) { // If the global rollback needs to be identified, then rollback unexpectedRollback = status.isGlobalRollbackOnly(); } // Roll back by throwing an exception if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction silently rolled back because it has been" + " marked as rollback-only"); } } catch (UnexpectedRollbackException ex) { // Triggering the after completion event when an exception is thrown triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); throw ex; } catch (TransactionException ex) { // Determine if you need to roll back when commit fails, rollback, or trigger the after completion event if (isRollbackOnCommitFailure()) { doRollbackOnCommitException(status, ex); } else { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); } throw ex; } catch (RuntimeException | Error ex) { // If the before completion event is not triggered, it is triggered again if (!beforeCompletionInvoked) { triggerBeforeCompletion(status); } doRollbackOnCommitException(status, ex); throw ex; } try { // Triggering the after commit event triggerAfterCommit(status); } finally { // Triggering the after completion event triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } } finally { // Clean up the transaction status information saved in the current thread cleanupAfterCompletion(status); } }
6. summary
This paper first explains the overall structure of Spring transactions, then explains how Spring handles transaction propaganda, and then introduces in detail the principles of transaction creation, rollback and submission.