What is a transaction? Little A arranged A long, long sequence of dominoes, while little C held up his camera and said, “As long as you get it right, this must be A cool piece of material.” Well, it’s A transaction, indivisible, with only success or failure, and no success. In some sense, if little A’s domino sequence is perfect, then little C can get what he wants, but as long as there are spotty points in the middle, little A and little C have nothing.

preface

The introductory example may not be very accurate, so let’s take a look at Wikipedia’s definition of a transaction:

Transaction processing is information processing in computer science that is divided into individual, indivisible operations called transactions. Each transaction must succeed or fail as a complete unit;

Transaction processing is information processing in computer science. Atomic, indivisible operations are called transactions, and each complete unit has only two states: success or failure.

The actual operation of a Transaction is at the database level and won’t be covered in this article, but Spring Transaction is more like a butler at the top of the DB. This time the focus will be on how Spring Transaction, based on SpringAOP, enables us to start, roll back, and commit transactions without being aware of using them. SpringAOP implementation has been analyzed last time, so please go to the **SpringAOP chapter ** for a more enjoyable reading experience.

Ps: All logic analysis of this content is based on the somke-test-data-JDBC project of spring-boot (v2.2.9.RELEASE). Git address: github.com/spring-proj… .

The affairs of generation of Peking ape man were realized

Before we get into Spring Transaction in earnest, let’s take a look at starting, rolling back, and committing a Transaction in the Pekingese era of pure JDBC.

public void updateSomething(Connection con, Coffee coffee, MarketGoods goods) {
    // Define the statement to execute
    String updateCoffeeSql = "UPDATE coffee SET price = ? WHERE type = ?";
    String updateMarketSql = "UPDATE market SET sale = sale + ? WHERE coffee_type = ?";
    try (
        PreparedStatement updateCoffee = new PreparedStatement(updateCoffeeSql);
        PreparedStatement updateMarket = new PreparedStatement(updateCoffeeSql)
    ) {
        // Set the database connection to manual commit (that is, turn on transactions)
        con.setAutoCommit(false);
        // Assign values to Sql parameters
       	updateCoffee.setBigDecimal(1, coffee.getPrice());
        updateCoffee.setString(2, coffee.getString());
        // Perform the update operationupdateCoffee.executeUpdate(); . doSomethingOthers(); updateMarket.setInt(1, goods.getSale());
        updateMarket.setString(2, goods.getCoffeeType());
        updateMarket.executeUpdate();
        
        // Commit after all operations are completed
        con.commit();
    } catch (SQLException e) {
        // Rollback the transaction after logic failure
        logger.warn("execute transaction wrong", e);
        if(con ! =null) {
            try {
                // Rollback operation
            	con.rollback();
            } catch (SQLException excep) {
                logger.warn("rollback failed", e); }}}}Copy the code

A concrete implementation of Spring Transaction

On the top we looked at the native JDBC-based transaction implementation logic. Overall, it is quite tedious. The local logic is set to manual commit, and commit and rollback operations are common when execution completes or fails. So how does Spring Transaction, as the Transaction manager, do this?

Preparation: Butler reports

As I mentioned in the introduction that Spring Transaction is AOP based, there must be an Advice as the concrete implementer of the Transaction logic. Just to be clear, the implementer is a MethodInterceptor class called TransactionInterceptor. Where does it come from? Don’t worry. Let’s take our time.

In the spring-boot-autoconfigure project, where everything can be traced back, you can see two class configurations in the spring.factories file. TransactionAutoConfiguration and DataSourceTransactionManagerAutoConfiguration, In DataSourceTransactionManagerAutoConfiguration automatic configuration class, according to our application data sources registered DateSourceTransactionManager transaction manager with the IoC container, While TransactionAutoConfiguration will be done in a transaction manager after registration.

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class)
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {...@Configuration(proxyBeanMethods = false)
    @EnableTransactionManagement(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
    public static class CglibAutoProxyConfiguration {}}Copy the code

According to the execution of the Spring automatic assembly logic, CglibAutoProxyConfiguration is considered in line with the configuration rules of configuration class, regardless of the definition of configuration class notes and annotations, the definition of configuration rules So now we can focus is on the @ EnableTransactionManagement this annotation. Huh? You ask me what Spring autowiring is? Take a look at this. It’s a long one, but it might solve a little bit of your puzzle.

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass(a) default false;
    
    AdviceMode mode(a) default AdviceMode.PROXY;
    
    int order(a) default Ordered.LOWEST_PRECEDENCE;
}

// TransactionManagementConfigurationSelector
protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        case PROXY:
            return new String[] {AutoProxyRegistrar.class.getName(),
                                 ProxyTransactionManagementConfiguration.class.getName()};
        case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
        default:
            return null; }}Copy the code

This annotation introduced TransactionManagementConfigurationSelector configuration choice, the cut surface of the default mode is the PROXY, according to section in the configuration choice class mode, Loads a class called ProxyTransactionManagementConfiguration configuration. You can also use ASPECTJ instead of Spring’s default PROXY mode, which will implement Spring transactions. This article will focus on the implementation of SpringAOP transactions. All of AspectJ’s transaction implementations are beyond analysis.

In the configuration of ProxyTransactionManagementConfiguration, we finally found the original target TransactionInterceptor, There is another friend AnnotationTransactionAttributeSource, it both supplement each other, but we can say it later.

Claim service object

Once we have identified the executor of the transaction logic, we need to know how it binds to the class in our application that requires the transaction to be executed. The last letter said:

After Spring retrieves all Advice from the cache, it sends the Advice list and Bean meta-information to AopUtils#findAdvisorsThatCanApply for lookup and filtering and returns a matching subset of Advice. Its internal logic by org. Aspectj. Weaver. Patterns. Pointcut# is responsible for the match. The Pointcut#match method determines whether each method in the target class meets the requirements of the aspect, and enhances the target class if any method meets the requirements.

Find the parcel we TransactionInterceptor Advice class BeanFactoryTransactionAttributeSourceAdvisor pointcut its member properties match () method.

@Override
public boolean matches(Method method, Class
        targetClass) {
    TransactionAttributeSource tas = getTransactionAttributeSource();
    return (tas == null|| tas.getTransactionAttribute(method, targetClass) ! =null);
}
Copy the code

In getTransactionAttribute, the checked methods are cached. When the transaction logic is executed later, if the method in the cache is marked NULL_TRANSACTION_ATTRIBUTE, the transaction logic is skipped. Due to space constraints, we focus on a small section of the entire getTransactionAttribute method, and you can read the remainder logic if you are interested.

// AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, @NullableClass<? > targetClass) {
    /* * Due to Cglib's subclass-based enhanced logic, there is no way to inherit private methods from the parent class * so methods to be managed by Spring Transaction cannot be private methods * Ps: SpringAOP's cglib implementation needs to be consistent with jdkproxy
    if(allowPublicMethodsOnly() && ! Modifier.isPublic(method.getModifiers())) {return null;
    }

    / * * methods on the definition and implementation of may in the interface and implementation classes gave no rewriting method implementation * so you need to regain method from the defining methods of the target class, * um participant ICustomerService. SaveCustomer () method is the default implementation, * But CustomerServiceImpl does not give a rewrite implementation of saveCustomer() */
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // Looks for the @Transactional annotation attribute ona special method (or on the current method if no special method exists)
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if(txAttr ! =null) {
        return txAttr;
    }

    // Look for the annotation attribute @Transactional on the target class of the special method
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    if(specificMethod ! = method) {// Go back to the original method and look for the @Transactional annotation attribute
        txAttr = findTransactionAttribute(method);
        if(txAttr ! =null) {
            return txAttr;
        }
         // Go back to the original method definition class and look for the @Transactional annotation attribute
        txAttr = findTransactionAttribute(method.getDeclaringClass());
        if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
            returntxAttr; }}return null;
}
Copy the code

In computeTransactionAttribute method, we can see the Spring will search @ Transactional annotation on the target class, if comments, Will build configuration information in the internal parser for RuleBasedTransactionAttribute objects and cache into attributeCache object. The underlying java.lang.Reflect reflection is used to retrieve class annotation information. When Spring finds a class/method marked with the @Transactional annotation, it adds a transaction interceptor to the target class’s chain of cuts for subsequent transaction wrapping operations.

゚ д ゚(blue)

The official documentation for SpringFramWork 2.0.x has this description: “The Spring team’s recommendation is that you only annotate concrete classes with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this will only work as you would expect it to if you are using interface-based proxies. The fact that annotations are not inherited means that if you are using class-based proxies then the transaction settings will not be recognised by the class-based proxying infrastructure and the object will not be wrapped in a transactional proxy (which would be decidedly bad)”, simple translation: “The Spring team recommends that the @Transactional annotation be marked ona specific implementation class rather than on an interface. Although annotations can be marked on an interface or interface method, they only apply if they are an interface-based proxy (i.e. JDKProxy). Due to the non-inheritability of annotations, When using a class-based proxy (that is, CglibProxy), transaction Settings are not recognized so that objects are not managed by the transaction manager. This is why most blogs that exist on the web today feed the @Transactional annotation with “markup on the interface is not recommended.”

But according to the (their) test and elimination source logic reading, in SpringBoot2.2.9. RELEASE (SpringFramework5.2.8. RELEASE) version, the Spring’s transaction managed by the agent is not the influence of additional or JDKProxy, This is related to whether the @Transactional annotation meta information is available on the target method via getTransactionAttribute(). Since the source version of SpringFramework on Github can only be traced back to 3.0.x at the earliest, it is still impossible to understand the implementation of SpringTransaction at that time. While it’s true that it’s still not recommended to put the @Transactional annotation on an interface (because interfaces are abstract and transactions are an implementation’s concern), the reason really doesn’t matter if it’s a class-based proxy implementation or an interface-based proxy implementation. (there is no relevant description in the SpringFramwork5 documentation, of course, if there is a problem with my testing steps, I hope you can correct it.)

Housekeeping: housekeeping

In the previous section, we saw how SpringTransaction’s transaction manager came to be and how it found the objects it needed to serve. Once the target has been found, the bushi needs to get the migrant TransactionManager to work. Because the previous article on SpringAOP process analysis is based on Cglib enhanced, so this part will also follow the Cglib process, this may have more source content, but also please read patiently. The JDKProxy process is generally similar, so I won’t go into details here.

After the Spring application is started, the IoC container stores our enhanced class instances, and the classes or methods that we need to execute in transaction mode will be intercepted by the TransactionInterceptor TransactionInterceptor in the interceptor during execution to carry out the transaction wrapping operation that Spring abstracts for us. Let me take a look at the TransactionInterceptor method.

public Object invoke(MethodInvocation invocation) throws Throwable {
    // Get the target class of the intercepted methodClass<? > targetClass = (invocation.getThis() ! =null ? AopUtils.getTargetClass(invocation.getThis()) : null);

    /* * Call the invokeWithinTransaction method * of the parent class TransactionAspectSupport, and after completing the requirement determination and transaction startup logic for transaction execution, Call the invocation#proceed() method * to proceed with the subsequent logical execution of the method and wait for a normal or abnormal return of the method for subsequent transaction commit/rollback processing */
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
Copy the code

The logic in TransactionInterceptor is very simple. The main core method is TransactionAspectSupport#invokeWithinTransaction. So I will take part of what I think is important points out, the rest of the logic of some auxiliary children can also be interested in their own into the method to have a look.

protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {

    / * * remember we mentioned earlier on the method of @ Transactional annotation information for caching TransactionAttributeSource * This will be cached based on the method and the target class previously retrieved from the TransactionInterceptor invoke method * because in enhanced logic, whenever a method meets transaction injection requirements, The class is enhanced * so the remaining methods are also transacted to perform the necessary judgments and cached */
    TransactionAttributeSource tas = getTransactionAttributeSource();
    /* * if the method has been scanned before, the annotation information will be read directly from the cache. If the method is marked NULL_TRANSACTION_ATTRIBUTE, the annotation information will be returned null. Otherwise, an annotation scan will be performed on the node and the cache will return */
    finalTransactionAttribute txAttr = (tas ! =null ? tas.getTransactionAttribute(method, targetClass) : null);
    "/* * methods that have @tranactional annotations and actional TransactionManager names" are actional "will look for the specified TransactionManager in the IoC container, whereas" actional "methods look for TransactionManager objects in the IoC container." if only one exists, If there are more than one transaction manager, you need to specify one as the primary transaction manager. Otherwise, empty */ is returned
    final TransactionManager tm = determineTransactionManager(txAttr);

    /* * The implementation of reactive transactions... * It's not our focus... I'll skip */ this time

    // convert transactionManager into a central interface to ensure that multiple types of transactionManager execution flows are uniform
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    // Get and enter the qualified name of the method as the default name for creating transactions later
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null| |! (ptminstanceof CallbackPreferringPlatformTransactionManager)) {
        If the transaction attribute is null, the transaction will not be created
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

        Object retVal;
        try {
            // Because the connector is a chained operation, the subsequent process of the method is executed on this call and waits for the subsequent process to return
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // When the method subsequent process execution fails, the current transaction is completed and some one-transaction processing logic, such as rollback, is performed
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // Reset transaction information (involving nested transaction logic)
            cleanupTransactionInfo(txInfo);
        }
        // Perform some subsequent operations, such as commit, when the transaction returns normally
        commitTransactionAfterReturning(txInfo);
        // Returns the result of the process
        return retVal;
    } else {
        / * * * CallbackPreferringPlatformTransactionManager affairs manager through the transaction in the process of transaction callback commit rollback operations such as * not this paper concern the skip * /}}Copy the code

The execution process of the whole transaction has been roughly reviewed first. The process is not very complicated, and I have added notes on the relevant methods. From the notes, we can pick out several core methods:

  • TransactionAspectSupport#createTransactionIfNecessaryCreate transaction logic
  • TransactionAspectSupport#completeTransactionAfterThrowingTransaction exception flow handling logic
  • TransactionAspectSupport#commitTransactionAfterReturningTransaction normal flow processing logic

Let’s just go down one by one.

Create a transaction – Make a good start

// TransactionAspectSupport#createTransactionIfNecessary
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    // If the Transactional name is not specified on the @Transactional annotation, the method qualified name is used as the transaction name
    if(txAttr ! =null && txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName(a) {
                returnjoinpointIdentification; }}; } TransactionStatus status =null;
    / * *, when there is a method of transaction related tags and applications within the transaction manager will through * PlatformTransactionManager# getTransaction get transaction sample create * /
    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"); }}}// Encapsulate transaction information into a transactionInfo object and bind it to the current thread
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
Copy the code

When methods there is no transaction attribute configuration, according to the operation logic, not to create related affairs, and transaction creation process getTransaction, are abstract in AbstractPlatformTransactionManager class, The method obtains the transaction propagation level, isolation level, read-only or not, and expiration time of the method transaction configuration, and then performs logical operations based on the propagation level of the transaction, for example, PROPAGATION_MANDATORY throws an exception if no transaction exists. We won’t go into details about the logical actions that each propagation level performs, but we’ll focus on startTranaction and doBegin logic.

// AbstractPlatformTransactionManager#startTransaction
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
			boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
    /* * Gets whether the current transaction manager is executing synchronically, and if so, the transaction properties are bound into the current thread */ in the subsequent prepareSynchronization method
    booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, suspendedResources);
    // Specific transaction start logic
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}

/ * * we take a look at somke - test - data - the default JDBC project DataSourceTransactionManager TransactionManager * * open transaction process is how to operate DataSourceTransactionManager#doBegin */ 
protected void doBegin(Object transaction, TransactionDefinition definition) {
    // Get the transaction object
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        // Try to get a database connection from the transaction object, if no connection exists, a new connection is created and bound to the transaction
        if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection();if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
		
        // mark the transaction as synchronous execution
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        // Get the connection
        con = txObject.getConnectionHolder().getConnection();

        // Perform some prefixes on the connection, such as setting the connection read-only, setting the connection isolation level, and so on
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        // Override the isolation level and whether it is read-only back to the transaction
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());

        / * * if the database connection is configured to automatically submit, in this change to manual submitted to unlock the transaction * why not directly modify but to add a layer of judgment, the Spring team is set to connect to manually submit * for JDBC driver is a very heavy operation, they hope in this setting does not have unnecessary time to * / 
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            // Set the connection to start the transaction
            con.setAutoCommit(false);
        }
	
        /* * If the TRANSACTION is read-only and the TRANSACTION manager configuration class display specifies read-only *, a session is opened here and the 'SET TRANSACTION READ ONLY' display sets the TRANSACTION to read-only */
        prepareTransactionalConnection(con, definition);
        // mark the transaction as enabled
        txObject.getConnectionHolder().setTransactionActive(true);

        // Set transaction expiration time
        int timeout = determineTimeout(definition);
        if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }// If the transaction is a newly opened transaction, the connection, data source information is bound to the current thread
        if(txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}catch (Throwable ex) {
        // Release the connection when transaction start exception occurs
        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

In the doBegin method, the transaction opening logic that we were originally looking for at the beginning of the content, “Pekingese generation”, has already appeared. On data source and a small key, SpringTransaction method, connection, transaction information, information such as entry point is bound to the thread operation, called TransactionSynchronizationManager logic, are implemented through the ThreadLocal object, This is why calling other transactional methods on a method marked as a transaction through asynchronous logic invalidates a transaction. We’ll talk about that later when we see this point, maybe… Consider the logic of how to ensure that transactions do not fail when multiple threads execute transaction methods.

Rollback transaction – Oops, error

// TransactionAspectSupport#completeTransactionAfterThrowing
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
        // Get the method transaction configuration and determine if the exception needs to be rolled back
        if(txInfo.transactionAttribute ! =null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // Perform a rollback
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            catch (Exception ex2) {
               // ...}}else {
            // If the rollback does not need to be performed according to the configuration, the commit operation will still be performed
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            catch (Exception ex2) {
                // ...}}}}/* * Spring performs a series of checks on the state of the rollback, such as whether the transaction state is completed, whether the transaction has savepoints, etc. * After the rollback, Spring resets the transaction information bound to the thread. Let's look at the rollback of the specific operation logic * DataSourceTransactionManager# doRollback * /
protected void doRollback(DefaultTransactionStatus status) {
    // Get the transaction object
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    // Get the connection
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) {
        logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
    }
    try {
        // Perform familiar rollback operations
        con.rollback();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back JDBC transaction", ex); }}Copy the code

Another point to note in the transaction rollback logic is whether or not the TransactionAttribute#rollbackOn method needs to be rolled back, As a result of the default build SpringTransactionAnnotationParser is RuleBasedTransactionAttribute, During parsing, rollbackFor and rollbackForClassName are encapsulated as RollbackRuleAttribute objects. NoRollbackFor and noRollbackForClassName are encapsulated into NoRollbackRuleAttribute objects and stuffed into the rules list to determine the basis for subsequent rollback operations, so let’s take a look at its rollback logic.

// RuleBasedTransactionAttribute#rollbackOn
public boolean rollbackOn(Throwable ex) {
    RollbackRuleAttribute winner = null;
    int deepest = Integer.MAX_VALUE;

    if (this.rollbackRules ! =null) {
        // Get all rollback rules
        for (RollbackRuleAttribute rule : this.rollbackRules) {
            // Compare rollback rules and get the depth
            int depth = rule.getDepth(ex);
            if (depth >= 0 && depth < deepest) {
                // Use the exception with the least depth and matching as the preferred exceptiondeepest = depth; winner = rule; }}}/* * If there is no exception match, the default rollback is performed */
    if (winner == null) {
        return super.rollbackOn(ex);
    }

    // If the final preference is the exception type that does not need to be rolled back, the rollback operation is not carried out, otherwise, the rollback operation is carried out
    return! (winnerinstanceof NoRollbackRuleAttribute);
}
Copy the code

Several things can be gleaned from the above logic:

  1. All of theRuntimeExceptionAnd Error are rolled back unlessRuntimeExceptionIt’s configurednoRollbackForIn the list
  2. For example, if the subclass exception is configured as no rollback and the parent class exception is configured as rollback, the subclass exception is not rolled back because the subclass exception depth is smaller

Commit business – It’s done

// TransactionAspectSupport#commitTransactionAfterReturning
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
        // Transaction manager commit logictxInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); }}/* * * The transaction's savepoint is freed and the specific commit is performed. * After the commit is completed, the transaction object properties bound to the thread are reset. But there was a RuntimeException if you submit or Error * will still be for rollback operation * DataSourceTransactionManager# doCommit * /
protected void doCommit(DefaultTransactionStatus status) {
    // Get transaction information
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    // Get the connection
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        // Perform the specific commit operation
        con.commit();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit JDBC transaction", ex); }}Copy the code

The tail

I hope you know why Spring transactions fail during execution. For example, beans that are not managed as Springbeans by the IoC container will not be enhanced. Making a this. XXX method call in this class also does not trigger transaction logic because the class object is used instead of the enhanced object; The @Transactional annotation is applied to non-public methods because Cglib does not enhance non-public methods; The exception that you caught with a try/catch does not trigger a rollback, because the TransactionInterceptor is a rollback logic in the outermost catch.

In short, Spring is a monster, and I don’t expect that I will understand it thoroughly one day. I just hope that when I encounter a problem, I will know where the problem is and how to solve it.

Ps: RECENTLY very busy, but finally finished this content, very long, I also know that it is not easy to really read this content, I will be ready to go home for the Chinese New Year, I do not know how many people will stay in the workplace for the Chinese New Year… Anyway, happy New Year, and ah… May you have someone to share your joys and sorrows in the New Year. I’m Xiao, happy New Year.