Spring transactions rely on AOP. The transaction execution process is to add AOP facet logic when the business method is executing. It is intercepted by the TransactionInterceptor class. We use this as the entry point to analyze the whole process.
1, TransactionInterceptor
public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... // Call the parent method directly return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
2, TransactionAspectSupport
The invokeWithinTransaction method adds transaction support to the business method through the around aspect. The source code is simplified and easy to analyze.
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. // Get some properties of the configured transaction TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); // <1> , get transaction manager according to property final TransactionManager tm = determineTransactionManager(txAttr); if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) { // <2> , reactive transaction manager } // Convert to PlatformTransactionManager PlatformTransactionManager ptm = asPlatformTransactionManager(tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. // <3> . standard transaction flow } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. // <4> , CallbackPreferringPlatformTransactionManager transaction support process }
[1] , get the specified transaction manager from the current Spring container. You can get or get the default transaction manager by name.
[2] , transaction support for reactive programming, not in-depth
[3] . standard transaction support process
[4] , CallbackPreferringPlatformTransactionManager transaction support
3, Standard transaction support
This paper mainly analyzes the transaction flow under the background of standard declaration transaction
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. [1],Get transaction information. This object contains transaction manager, transaction attribute definition and transaction status TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. // Call the target method, our business method, which may contain other AOP s retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception // [2] , rollback transaction completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } if (vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... TransactionStatus status = txInfo.getTransactionStatus(); if (status != null && txAttr != null) { retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } } //[3] , commit transaction commitTransactionAfterReturning(txInfo); return retVal; }
3.1 obtaining transaction information
This object contains transaction manager, transaction attribute definition and transaction state, including transaction propagation behavior and isolation level implementation.
The core is to obtain the state information of transaction, which includes the implementation of propagation behavior. See: https://my.oschina.net/cregu/blog/3210239
3.2 transaction rollback
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { // Rollback only if set correctly try { txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } } else { // Rollback is not currently required // We don't roll back on this exception. // Will still roll back if TransactionStatus.isRollbackOnly() is true. try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } } } private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { try { if (status.hasSavepoint()) { // Rollback to savepoint status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { // The new transaction is rolled back directly. For jdbc, Connection.rollback() is called doRollback(status); } else { // Participating in larger transaction // There are two situations, one is to join the parent transaction, the other is to run the non transaction if (status.hasTransaction()) { // Currently joined to parent transaction if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { // For jdbc transactions, the rollbackOnly=true flag is set doSetRollbackOnly(status); } else { // Non transactional run, no rollback if (status.isDebug()) { logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } } else { // Non transactional run, no rollback logger.debug("Should roll back transaction but cannot - no transaction available"); } } } } finally { cleanupAfterCompletion(status); } }
1. Transaction rollback needs to judge the conditions. Rollback can only be performed if the settings are correct;
2. The rollback operation will also depend on the situation and be processed according to different propagation behaviors
3. If it is a new transaction, call jdbc Connection.rollback()
4. If it is a child of the parent transaction, set the rollback flag in the child transaction
5. Non transactional mode and no rollback
3.3 transaction submission
public final void commit(TransactionStatus status) throws TransactionException { 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 (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); } processRollback(defStatus, false); return; } if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { // Transaction is set with rollback flag, so rollback is needed here processRollback(defStatus, true); return; } processCommit(defStatus); } private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { boolean beforeCompletionInvoked = false; try { boolean unexpectedRollback = false; if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Releasing transaction savepoint"); } unexpectedRollback = status.isGlobalRollbackOnly(); status.releaseHeldSavepoint(); } else if (status.isNewTransaction()) { // The new transaction is committed. jdbc calls Connection.commit(); if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); doCommit(status); } else if (isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = status.isGlobalRollbackOnly(); } } } finally { cleanupAfterCompletion(status); } }
1. Before the transaction is submitted, some judgment will be made. If the child transaction is set with rollback flag, rollback operation is required here
2. If the transaction commit condition is met, the jdbc Connection.commit() is called if it is a new transaction
3. Transaction commit failure will also be rolled back