An overview of the

We usually use transactions in our projects, and simply add a @Transactional annotation to make the transaction definition work. Have we thought about the meaning of this annotation and the rationale behind it? This article will elaborate on the Spring transaction core, the working principle of @Transactional annotation, and the propagation mechanism of transactions.

Spring Transaction Core

Spring transactions are handled in two ways: programmatic transactions and declarative transactions. The way we usually annotate is declarative transactions, if we start a transaction manually, complete it normally, commit it, fallback it’s a programmatic transaction. We mainly describe the implementation of declarative transactions

Enabling transaction Management

  1. @ EnableTransactionManagement are defined as follows, in which the load a TransactionManagementConfigurationSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
	// ...
}
Copy the code
  1. TransactionManagementConfigurationSelector is a ImportSelector loaded AutoProxyRegistrar and two class ProxyTransactionManagementConfiguration.
@Override
protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        case PROXY:
            / / to add AutoProxyRegistrar and ProxyTransactionManagementConfiguration corresponding bean in the Spring
            return new String[] {AutoProxyRegistrar.class.getName(),
                    ProxyTransactionManagementConfiguration.class.getName()};
        case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
        default:
            return null; }}Copy the code

AutoProxyRegistrar is actually an automated agent creator

- AutoProxyRegistrar#registerBeanDefinitions()
  // Register the automatic proxy creator
  -- AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
  / / registered a InfrastructureAdvisorAutoProxyCreator it also implements the BeanPostProcessor
  -- registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
Copy the code
  1. ProxyTransactionManagementConfigurationIt’s a proxy logicAdvisorContains thePoincutAdvice
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(a) {

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		// indicates the pointcut
		advisor.setTransactionAttributeSource(transactionAttributeSource());
		// represents advice agent logic
		advisor.setAdvice(transactionInterceptor());
		if (this.enableTx ! =null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

	/ / @ TransactionAttributeSource said the information contained in the Transaction
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource(a) {
		return new AnnotationTransactionAttributeSource();
	}

	// The TransactionInterceptor is the logical detail of a proxy
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(a) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		// The @Transaction annotation information held in the interceptor
		interceptor.setTransactionAttributeSource(transactionAttributeSource());
		if (this.txManager ! =null) {
			interceptor.setTransactionManager(this.txManager);
		}
		returninterceptor; }}/ / TransactionInterceptor class
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor.Serializable {

    // ...
    
    @Override
    @Nullable
    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...
        // Execute the proxy logic
        returninvokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }}/ / BeanFactoryTransactionAttributeSourceAdvisor defined
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
    / / constructs a Pointcut, TransactionAttributeSource for AnnotationTransactionAttributeSource the implementation of the object
    / / in the Pointcut matching class, will go to check on the class for using AnnotationTransactionAttributeSource @ Transaction annotations
    / / in the Pointcut matching method, will go to check methods for using AnnotationTransactionAttributeSource @ Transaction annotations
    private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
        @Override
        @Nullable
        protected TransactionAttributeSource getTransactionAttributeSource(a) {
            returntransactionAttributeSource; }};@Override
    public Pointcut getPointcut(a) {
        return this.pointcut; }}/ class / / TransactionAttributeSourcePointcut# matches matching method above contains @ Transaction annotations
abstract class TransactionAttributeSourcePointcut@ extends StaticMethodMatcherPointcut implements Serializable {

    /** * Determines whether a method or class exists@TransactionNote *@param method the candidate method
     * @param targetClass the target class
     * @return* /
    @Override
    public boolean matches(Method method, Class
        targetClass) {
        if (TransactionalProxy.class.isAssignableFrom(targetClass) ||
                PlatformTransactionManager.class.isAssignableFrom(targetClass) ||
                PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) {
            return false;
        }
        TransactionAttributeSource tas = getTransactionAttributeSource();
        // Whether the @Transaction annotation exists on a class or method
        / / call AnnotationTransactionAttributeSource getTransactionAttribute in the parent class method
        // If there is a @transaction annotation in the segment, the annotation information is not equal to null
        return (tas == null|| tas.getTransactionAttribute(method, targetClass) ! =null); }}//tas.getTransactionAttribute(method, targetClass)
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @NullableClass<? > targetClass) {
    if (method.getDeclaringClass() == Object.class) {
        return null;
    }

    // First, see if we have a cached value.
    Object cacheKey = getCacheKey(method, targetClass);
    TransactionAttribute cached = this.attributeCache.get(cacheKey);
    if(cached ! =null) {
        // Value will either be canonical value indicating there is no transaction attribute,
        // or an actual transaction attribute.
        if (cached == NULL_TRANSACTION_ATTRIBUTE) {
            return null;
        }
        else {
            returncached; }}else {
        // We need to work it out.
        // Get the @Transactional annotation information
        TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
        // Put it in the cache.
        if (txAttr == null) {
            this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
        }
        else {
            String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
            if (txAttr instanceof DefaultTransactionAttribute) {
                ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
            }
            this.attributeCache.put(cacheKey, txAttr);
        }
        returntxAttr; }}//computeTransactionAttribute(method, targetClass);
protected TransactionAttribute computeTransactionAttribute(Method method, @NullableClass<? > targetClass) {
    // Don't allow no-public methods as required.
    AllowPublicMethodsOnly defaults to false, so putting the @Transactional annotation ona public method doesn't work
    if(allowPublicMethodsOnly() && ! Modifier.isPublic(method.getModifiers())) {return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if(txAttr ! =null) {
        return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    if(specificMethod ! = method) {// Fallback is to look at the original method.
        txAttr = findTransactionAttribute(method);
        if(txAttr ! =null) {
            return txAttr;
        }
        // Last fallback is the class of the original method.
        txAttr = findTransactionAttribute(method.getDeclaringClass());
        if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
            returntxAttr; }}return null;
}
Copy the code

The takeaway here is that the @Transactional annotation is valid for public methods, and all other methods are invalid

TransactionInterceptor Execution of a transaction method

  1. The TransactionInterceptor Invoke method is the core method for transaction execution
protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {

    // If the transaction attribute is null, the method is non-transactional.
    TransactionAttributeSource tas = getTransactionAttributeSource();
    // Get the @Transactional annotation on the current method or class
    finalTransactionAttribute txAttr = (tas ! =null ? tas.getTransactionAttribute(method, targetClass) : null);
    // Get information about the @Transactional annotation
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    // Generate a unique identifier based ona method in the currently executing class and information from the @Transactional annotation. This identifier is used as the transaction name
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null| |! (tminstanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        // Create a transaction and get information about the current transaction, which is needed later to commit or roll back
        / / such as: cn.edu.xxx.tx.service.PersonService.test
        // The default is the class name full path + method name
        TransactionInfo txInfo = createTransactionIfNecessary(tm, 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.
            // Execute the business method
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // target invocation exception
            Rollback rollback rollback rollback rollback rollback rollback rollback rollback rollback
            completeTransactionAfterThrowing(txInfo, ex);
            // Exception restriction finally logic throws an exception
            throw ex;
        }
        finally {
            // Delete transaction
            cleanupTransactionInfo(txInfo);
        }
        // Commit the transaction
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();

        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                    return invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                    if (txAttr.rollbackOn(ex)) {
                        // A RuntimeException: will lead to a rollback.
                        if (ex instanceof RuntimeException) {
                            throw (RuntimeException) ex;
                        }
                        else {
                            throw newThrowableHolderException(ex); }}else {
                        // A normal return value: will lead to a commit.
                        throwableHolder.throwable = ex;
                        return null; }}finally{ cleanupTransactionInfo(txInfo); }});// Check result state: It might indicate a Throwable to rethrow.
            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);
            }
            throwex2; }}}Copy the code
  1. completeTransactionAfterThrowing(txInfo, ex);Exception handling, this will say yesRuntimeExceptionorErrorRollback is performed, other transactions are not rolled back.
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                    "] after exception: " + ex);
        }
        // If the current exception requires a rollback, perform the rollback, otherwise commit
        // The default is RuntimeException or Error
        if(txInfo.transactionAttribute ! =null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                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);
                throwex2; }}else {
            // We don't roll back on this exception.
            // Will still roll back if TransactionStatus.isRollbackOnly() is true.
            try {
                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);
                throwex2; }}}}Copy the code
  1. From this method we can see that we need to create one first in the process of usePlatformTransactionManager
@Bean
public DataSourceTransactionManager transactionManager(a) {
    return new DataSourceTransactionManager(dataSource());
}
Copy the code
  1. Transaction acquisitionTransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

    // If no name specified, apply method identification as transaction name.
    // Transaction name
    if(txAttr ! =null && txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName(a) {
                returnjoinpointIdentification; }}; }// Transaction object status
    TransactionStatus status = null;
    if(txAttr ! =null) {
        if(tm ! =null) {
            status = tm.getTransaction(txAttr);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                        "] because no transaction manager has been configured"); }}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
Copy the code
  1. We’re looking at how do we get transactionstm.getTransaction(txAttr)
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    Object transaction = doGetTransaction(); / / get transaction object, each will generate a DataSourceTransactionObject transaction object connectionHolder default is empty

    // Cache debug flag to avoid repeated checks.
    boolean debugEnabled = logger.isDebugEnabled();

    if (definition == null) {
        // Use defaults if no transaction definition given.
        definition = new DefaultTransactionDefinition();
    }

    if (isExistingTransaction(transaction)) {  // If a database link already exists, a transaction exists
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(definition, transaction, debugEnabled); // In the case of transactions, different propagation levels are processed
    }

    // Check definition settings for new transaction.
    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }

    // No existing transaction found -> check propagation behavior to find out how to proceed.
    // Transaction does not exist
    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) {
        / / hung
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + definition.getName() + "]." + definition);
        }
        try {
            // 
            booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER);// Transaction objects include:
            // 1. Transaction definition
            // 2. Transaction object
            // 3. Is it a new transaction
            DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
            // Start a new transaction and get a new connection object
            doBegin(transaction, definition);
            / / initialize TransactionSynchronizationManager properties
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throwex; }}else {
        // Do not doBegin, do not actually start transaction, that is, do not set Connection autoCommint to false, SQL is not executed in transaction
        // Create "empty" transaction: no actual transaction, but potentially synchronization.
        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);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(definition, null.true, newSynchronization, debugEnabled, null); }}Copy the code
  1. Let’s look at it againdoBeginHow is a transaction started
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        // Get a link from the dataSource if there is no link in the transaction object
        if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {/ / isSynchronizedWithTransaction said whether and transaction through, said a transaction will get a link from the dataSource
            // The default value is false, so when a transaction is started, a link is retrieved from the dataSource unless there is no link in the current transaction object
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true); // Set newConnectionHolder to true
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);// If the transaction is started, the transaction object already has a transaction link
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);// Set the isolation level of the database link. If the isolation level in the current transaction is not the same as the isolation level of the database, return the isolation level of the database and record it
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false); // Set database link autoCOMMIT to false
        }

        prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);

        // Set the timeout period
        int timeout = determineTimeout(definition);
        if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }// Bind the connection holder to the thread.
        // Save the link
        if (txObject.isNewConnectionHolder()) {
            // Set the newly generated database link to the ThreadLocal cache of the current threadTransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, obtainDataSource());
            txObject.setConnectionHolder(null.false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); }}Copy the code
  1. handleExistingTransactionIf the current transaction exists then the propagation mechanism of the transaction is used to determine the subsequent processing
private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
    }

    // Suspend the current transaction and execute it in a non-transactional manner
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction");
        }
        Object suspendedResources = suspend(transaction);
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(
                definition, null.false, newSynchronization, debugEnabled, suspendedResources);
    }

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction, creating new transaction with name [" +
                    definition.getName() + "]");
        }
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, suspendedResources);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throwbeginEx; }}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 =
                    prepareTransactionStatus(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.
            booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, null);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            returnstatus; }}// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    if (isValidateExistingTransaction()) {
        if(definition.getIsolationLevel() ! = TransactionDefinition.ISOLATION_DEFAULT) { Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();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()) {if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is"); }}}// If the other values run in the current transaction
    booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER);return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
Copy the code

Synchronous TransactionSynchronizationManager affairs manager

  1. When a transaction is started, the state of the transaction is synchronized to the transaction manager. In fact, its essence is to store the current transaction information toThreadLocalTo manage, call the methodprepareSynchronization(status, definition);
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    if(status.isNewSynchronization()) { TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());  TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( definition.getIsolationLevel() ! = TransactionDefinition.ISOLATION_DEFAULT ? definition.getIsolationLevel() :null); TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); TransactionSynchronizationManager.initSynchronization(); }}Copy the code
  1. Rollback of the transaction. First the transaction synchronizer is synchronized, and then determine if there is a safe pointsavepointIf so, back up to safety. Mysql doesn’tsavepointThe rollback is implemented.
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;

        try {
            // If there is a synchronizer BeforeCompletion
            triggerBeforeCompletion(status);

            If there is a safe point, roll back to the safe point
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
            }
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                doRollback(status);
            }
            else {
                // Participating in larger transaction
                if (status.hasTransaction()) {
                    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");
                }
                // Unexpected rollback only matters here if we're asked to fail early
                if(! isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback =false; }}}catch (RuntimeException | Error ex) {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw ex;
        }

        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

        // Raise UnexpectedRollbackException if we had a global rollback-only marker
        if (unexpectedRollback) {
            throw new UnexpectedRollbackException(
                    "Transaction rolled back because it has been marked as rollback-only"); }}finally {
        // Resume the currently pending transactioncleanupAfterCompletion(status); }}Copy the code

The @transactional annotation is used

@ the use of Transactional method, defined in the method, the said method on the transaction, we can through the transmission mechanism of propagation attribute to define the transaction

@Transactional
public void test(a) {
    userMapper.insert(user);

    userService.a();
}

@Transactional(propagation = Propagation.REQUIRED)
public void a(a) {
    userMapper.insert(user);
}
Copy the code

Execution process description:

  1. Generate the Test transaction state object
  2. Test transaction doBegin, gets and sets database connection 2825 into the test transaction state object
  3. Set the test transaction information to the transaction synchronization manager
  4. Execute test business logic method (can get test transaction information) 4.1. Generate a transaction state object, and can obtain the existing database connection in the current thread. If you need to start a new transaction, suspend the database connection 2825, which transfers the test transaction information from the transaction synchronization manager to the suspended resource object, and set the database connection in the current transaction state object to NULL 4.4. a transaction doBegin, Create a new database connection 2826 and set it to a transaction state object 4.5. Set transaction information of A to transaction synchronization manager 4.6. Execute a business logic method (transaction synchronization manager can be used to obtain transaction information of A) 4.7. Commit 4.8. After the commit, the suspended test transaction will be resumed. In this case, the information stored in the suspended resource object will be transferred back to the transaction synchronization manager
  5. Continue executing the test business logic method (you can still get the test transaction information)
  6. Commit using the Test transaction state object

Transaction propagation mechanism

Common examples of transaction definition

Case 1

Turn on the two default transaction definitions and call the test method and call the a method to implement the normal execution of the two methods.

@Component
public class UserService {
    @Autowired
    private UserService userService;
    @Transactional
    public void test(a) {
        // SQL in the test method
        userService.a();
    }
    @Transactional
    public void a(a) {
        // SQL in a method}}Copy the code

The propagation mechanism is REQUIRED by default, so the execution flow in this case is as follows:

  1. Create a new database connection to CONN
  2. Set conn’s autocommit to false
  3. Execute the SQL in the test method
  4. Execute the SQL in method A
  5. Execute Conn’s commit() method to commit

Case 2

When calling test, call a first, let test report an error, and implement the rollback of both methods at the same time.

@Component
public class UserService {
    @Autowired
    private UserService userService;
    @Transactional
    public void test(a) {
        // SQL in the test method
        userService.a();
        int result = 100/0;
    }
    @Transactional
    public void a(a) {
        // SQL in a method}}Copy the code

So the execution flow of the above case is as follows:

  1. Create a new database connection to CONN
  2. Set conn’s autocommit to false
  3. Execute the SQL in the test method
  4. Execute the SQL in method A
  5. An exception is thrown
  6. Perform conn’s rollback() method to rollback

Case 3

When calling test, call a first, let method A report an error, and implement the rollback of the two methods at the same time.

@Component
public class UserService {
    @Autowired
    private UserService userService;
    @Transactional
    public void test(a) {
        // SQL in the test method
        userService.a();
    }
    @Transactional
    public void a(a) {
        // SQL in a method
        int result = 100/0; }}Copy the code

So the execution flow of the above case is as follows:

  1. Create a new database connection to CONN
  2. Set conn’s autocommit to false
  3. Execute the SQL in the test method
  4. Execute the SQL in method A
  5. An exception is thrown
  6. Perform conn’s rollback() method to rollback

Scenario 4

When test is called, call a first. A is an independent transaction running, and let method A report an error. A rolls back.

@Component
public class UserService {
    @Autowired
    private UserService userService;
    @Transactional
    public void test(a) {
        // SQL in the test method
        userService.a();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void a(a) {
        // SQL in a method
        int result = 100/0; }}Copy the code

So the execution flow of the above case is as follows:

  1. Create a new database connection to CONN
  2. Set conn’s autocommit to false
  3. Execute the SQL in the test method
  4. Create a new database connection, conn2
  5. Execute the SQL in method A
  6. An exception is thrown
  7. Perform the rollback() method of conn2 to rollback
  8. Continue throwing exception
  9. Perform conn’s rollback() method to rollback

Frequently asked questions in development

  1. Scenario: Using AOP proxy to dynamically set the data source, in a transaction to call operations in different libraries, the second database execution SQL error message can not find the table, and set the data source switch failure.

Example: Using AOP in a project to dynamically set up a data source, call b after operation A (executed in the D1 library) opens a transaction with the @Transactional flag, and return a data source switch exception. Solution: @transactional (Propagation = Propagation.REQUIRES_NEW, RollbackFor = RuntimeException class). Spring – tx source location TransactionAspectSupport# invokeWithinTransaction createTransactionIfNecessary

Spring source code

  • Spring Startup Process
  • The life cycle of Spring beans
  • Spring property injection
  • Spring loop dependencies
  • Spring Aop
  • Spring transactions
  • Spring integration MyBatis
  • Spring FAQ