Spring 5 source deep parsing - transaction enhancer (100% understanding of transactions)

In the last article, we explained how the Advisor of a transaction registers with the Spring container, and how Spring configures transactions on classes that configure transactions. In fact, we used the AOP set. We also explained the Advisor, pointcut validation process. So far, the initialization of transactions has been completed. In the subsequent invocation process, if the method of the proxy class is invoked. When we use it, we call BeanFactoryTransaction AttributeSource Advisor, the Advisor enhancement method that we haven't mentioned yet. Remember, when we customize the tag, we register the Advice as a bean in the IOC container and inject it into the Advisor. This Advice will be used in the invoke method of the proxy class. Encapsulated in the interceptor chain, the function of the final transaction is embodied in advice, so let's first focus on the Transaction Interceptor class.

The TransactionInterceptor class inherits from MethodInterceptor, so invoking this class begins with its invoke method. First, preview this method:

@Override
@Nullable
public Object invoke(final 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...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

The key point is to enter the invokeWithinTransaction method:

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

    // If the transaction attribute is null, the method is non-transactional.
    TransactionAttributeSource tas = getTransactionAttributeSource();
    // Get corresponding transaction attributes
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    // Obtain beanFactory Medium transactionManager
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    // Construct method unique identifier (class).Methods, such as: service.UserServiceImpl.save)
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    // Declarative transaction processing
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Establish TransactionInfo
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // Executing the original method
            // Continue calling method interceptor chain,In general, the method of the target class will be invoked here.,as:AccountServiceImpl.save Method
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // Abnormal rollback
            completeTransactionAfterThrowing(txInfo, ex);
            // If an exception is thrown manually up, the following commit transaction will not execute
            // If an exception occurs in a subtransaction, the outer transaction code needs to catch Live sub-transaction code, or the outer transaction will roll back
            throw ex;
        }
        finally {
            // Eliminate information
            cleanupTransactionInfo(txInfo);
        }
        // Submission of affairs
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();
        try {
            // Programming Transaction Processing
            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 new ThrowableHolderException(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);
            }
            throw ex2;
        }
    }
}

Creating Transactional Info Objects

Let's first analyze the process of transaction creation.

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

    // If no name specified, apply method identification as transaction name.
    // If no name is specified, use method unique identifier and use DelegatingTransactionAttribute encapsulation txAttr
    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) {
            // Obtain TransactionStatus
            status = tm.getTransaction(txAttr);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                        "] because no transaction manager has been configured");
            }
        }
    }
    // According to the specified property and status Prepare one TransactionInfo
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

For the createTransactionlfNecessary function, there are several main things to do.

(1) Encapsulate the incoming Transaction Attribute instance using Delegating Transaction Attribute.

For the parameter txAttr of the incoming TransactionAttribute type, the current actual type is RuleBasedTransactionAttribute, which is generated when acquiring transaction attributes and is mainly used for data bearing. The reason why Delegating TransactionAttribute is used for encapsulation here is, of course, to provide more functions.

(2) Acquisition of transactions.

Transaction processing is, of course, transaction-centered, so acquiring transactions is the most important thing.

(3) Building transaction information.

Build Transaction lnfo based on the information obtained in the previous steps and return it.

Get transaction

The core is in the getTransaction method:

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    // Get one transaction
    Object transaction = doGetTransaction();

    boolean debugEnabled = logger.isDebugEnabled();

    if (definition == null) {
        definition = new DefaultTransactionDefinition();
    }
    // If a transaction already exists before that, it enters the method of existing transaction.
    if (isExistingTransaction(transaction)) {
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    
    // Transaction timeout settings validation
    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }

    // Going to this point indicates that there is no transaction at this time, if the propagation characteristics areMANDATORYThrow an exception
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    // If there is no transaction at this time, when the propagation characteristic is REQUIRED or NEW or NESTED Will enter if Statement block
    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED Need new business
        // Because there is no transaction at this time, the null Hang up
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
        }
        try {
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            // new One status,Store what you just created transaction,Then mark it as a new transaction!
            // Here transaction The latter parameter determines whether the transaction is new or not!
            DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
            // It's very important to open a new connection.
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // 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);
        }
        // All other propagation characteristics return an empty transaction. transaction = null
        //Currently, there is no transaction, and the communication mechanism=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,In these three cases, create an "empty" transaction
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
    }
}

Let's first look at how transaction was created:

@Override
protected Object doGetTransaction() {
    // Here DataSourceTransactionObject Is an internal class of transaction manager
    // DataSourceTransactionObject It's just one. transaction,Here new One came out
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    // The role of unbinding and binding is reflected at this time. If the current thread is bound, it will be removed. holder
    // For the first time conHolder Must be null
    ConnectionHolder conHolder =
    (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    // At this point holder Marked as an old one holder
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

The process of creating a transaction is simple, and then you can determine whether there is a transaction at the moment:

@Override
protected boolean isExistingTransaction(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

public boolean hasConnectionHolder() {
    return (this.connectionHolder != null);
}

The main criterion for judging whether there is a transaction is to get whether the transactionActive variable in the holder is true or not. If the holder enters the transaction for the first time, the holder directly judges that it does not exist for null, and if the transactionActive variable in the transaction for the second time is true (where will it be changed into true later), so as to judge whether there is a transaction at present.

So far, the source code has been divided into two processing lines

1. there are already transactions: isExistingTransaction() determines whether there is a transaction and there is transaction handleExistingTransaction (simultaneous interpreting) based on different propagation mechanisms.

2. there are no transactions at present: different communication mechanisms are handled differently.

Currently there is no transaction

If there is no transaction and the propagation feature is REQUIRED or NEW or NESTED, null will be suspended first. This suspension method will be discussed later. Then we will create a Default Transaction Status, mark it as a new transaction, and then execute doBegin(transaction, definition); this method is also a key method.

Mysterious and Key status Objects

TransactionStatus interface

public interface TransactionStatus extends SavepointManager, Flushable {
    // Returns whether the current transaction is a new transaction (otherwise it will participate in an existing transaction, or it may not run in a real transaction at first)
    boolean isNewTransaction();
    // Returns whether the transaction carries a savepoint internally, that is, it has been created as a nested savepoint-based transaction.
    boolean hasSavepoint();
    // Set the transaction to roll back only.
    void setRollbackOnly();
    // Whether the return transaction has been marked as rollback only
    boolean isRollbackOnly();
    // Refresh session to data store
    @Override
    void flush();
    // Returns whether the object has been completed, whether submitted or rolled back.
    boolean isCompleted();
}

 

Let's look again at the implementation class DefaultTransactionStatus

DefaultTransactionStatus

public class DefaultTransactionStatus extends AbstractTransactionStatus {

    //Transaction object
    @Nullable
    private final Object transaction;

    //Transaction object
    private final boolean newTransaction;

    private final boolean newSynchronization;

    private final boolean readOnly;

    private final boolean debug;

    //Transaction object
    @Nullable
    private final Object suspendedResources;
    
        public DefaultTransactionStatus(
            @Nullable Object transaction, boolean newTransaction, boolean newSynchronization,
            boolean readOnly, boolean debug, @Nullable Object suspendedResources) {

        this.transaction = transaction;
        this.newTransaction = newTransaction;
        this.newSynchronization = newSynchronization;
        this.readOnly = readOnly;
        this.debug = debug;
        this.suspendedResources = suspendedResources;
    }
    
    //slightly...
}

 

Let's look at this line of code Default Transaction Status = new Transaction Status (definition, transaction, true, new Synchronization, debug Enabled, suspended Resources);

// Here is the construction of a status Object Method
protected DefaultTransactionStatus newTransactionStatus(
    TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
    boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {

    boolean actualNewSynchronization = newSynchronization &&
        !TransactionSynchronizationManager.isSynchronizationActive();
    return new DefaultTransactionStatus(
        transaction, newTransaction, actualNewSynchronization,
        definition.isReadOnly(), debug, suspendedResources);
}

 

In fact, it encapsulates the transaction attribute definition, the newly created transaction, and sets the transaction status attribute to the new transaction, with the last parameter being the pending transaction.

Simply understand the key parameters:

The second parameter transaction: the transaction object, created at the beginning, is an internal class of the transaction manager.
The third parameter, newTransaction: Boolean value, an identifier, is used to determine whether a new transaction is committed or rolled back. In a commit or rollback method, a new transaction is committed or rolled back.
The last parameter suspendedResources: suspended object resources, the suspended operation will return to the old holder, encapsulate it with some transaction attributes as an object, that is, the suspendedResources, which will be placed in status, in the final cleanup method to determine whether there is a suspended object in status, and if there is one, it will restore it.

Next, let's look at the key code doBegin(transaction, definition);

 1 @Override
 2 protected void doBegin(Object transaction, TransactionDefinition definition) {
 3     DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 4     Connection con = null;
 5 
 6     try {
 7         // Judge if transaction No, holder If so, then go from dataSource Get a new connection in
 8         if (!txObject.hasConnectionHolder() ||
 9                 txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
10             //adopt dataSource Get connection
11             Connection newCon = this.dataSource.getConnection();
12             if (logger.isDebugEnabled()) {
13                 logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
14             }
15             // So, only transaction Medium holder When space-time, it will be set to new holder
16             // Encapsulate the acquired connection into ConnectionHolder,Then encapsulate transaction Of connectionHolder attribute
17             txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
18         }
19      //Set the new connection to Transaction Synchronization
20         txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
21         con = txObject.getConnectionHolder().getConnection();
22      //conn Setting transaction isolation level,read-only
23         Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
24         txObject.setPreviousIsolationLevel(previousIsolationLevel);//DataSourceTransactionObject Setting transaction isolation level
25 
26         // If automatic submission is switched to manual submission
27         if (con.getAutoCommit()) {
28             txObject.setMustRestoreAutoCommit(true);
29             if (logger.isDebugEnabled()) {
30                 logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
31             }
32             con.setAutoCommit(false);
33         }
34      // If read-only, execute sql Setting Transaction Read-Only
35         prepareTransactionalConnection(con, definition);
36         // Set up connection Holder's Transaction Open State
37         txObject.getConnectionHolder().setTransactionActive(true);
38 
39         int timeout = determineTimeout(definition);
40         if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
41             // Set timeout seconds
42             txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
43         }
44 
45         // Bind the currently acquired connection to the current thread
46         if (txObject.isNewConnectionHolder()) {
47             TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
48         }
49     }catch (Throwable ex) {
50         if (txObject.isNewConnectionHolder()) {
51             DataSourceUtils.releaseConnection(con, this.dataSource);
52             txObject.setConnectionHolder(null, false);
53         }
54         throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
55     }
56 }

 

conn sets transaction isolation level, read-only

@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
        throws SQLException {

    Assert.notNull(con, "No Connection specified");

    // Set read-only flag.
    // Setting Read-Only Identification for Data Connection
    if (definition != null && definition.isReadOnly()) {
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Setting JDBC Connection [" + con + "] read-only");
            }
            con.setReadOnly(true);
        }
        catch (SQLException | RuntimeException ex) {
            Throwable exToCheck = ex;
            while (exToCheck != null) {
                if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
                    // Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0
                    throw ex;
                }
                exToCheck = exToCheck.getCause();
            }
            // "read-only not supported" SQLException -> ignore, it's just a hint anyway
            logger.debug("Could not set JDBC Connection read-only", ex);
        }
    }

    // Apply specific isolation level, if any.
    // Setting the isolation level for database connections
    Integer previousIsolationLevel = null;
    if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
        if (logger.isDebugEnabled()) {
            logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
                    definition.getIsolationLevel());
        }
        int currentIsolation = con.getTransactionIsolation();
        if (currentIsolation != definition.getIsolationLevel()) {
            previousIsolationLevel = currentIsolation;
            con.setTransactionIsolation(definition.getIsolationLevel());
        }
    }

    return previousIsolationLevel;
}

 

We see that it's all set up through Connection.

Binding of Thread Variables

Let's look at the 47 lines of the doBegin method, which binds the currently acquired connection to the current thread and binds and unbinds around a thread variable in the Transaction Synchronization Manager class:

private static final ThreadLocal<Map<Object, Object>> resources =  new NamedThreadLocal<>("Transactional resources");

 

This is a static final decorated thread variable that stores a Map. Let's look at the 47-line static method, bindResource.

public static void bindResource(Object key, Object value) throws IllegalStateException {
    // As you can see from the above, the thread variable is one Map,And this Key Namely dataSource
    // this value Namely holder
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    // Get this thread variable Map
    Map<Object, Object> map = resources.get();
    // set ThreadLocal Map if none found
    if (map == null) {
        map = new HashMap<>();
        resources.set(map);
    }
    // New holder Act as value,dataSource Act as key Put in the current thread Map in
    Object oldValue = map.put(actualKey, value);
    // Transparently suppress a ResourceHolder that was marked as void...
    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
        oldValue = null;
    }
    if (oldValue != null) {
        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }    Thread.currentThread().getName() + "]");
    }
    // slightly...
}

 

Expanding Knowledge Points

Here is an extension. The database connection obtained in mybatis is obtained from ThreadLocal according to dataSource.

For example, the Executor doQuery method is called:

 

Data SourceUtils doGetConnection will be invoked eventually to get the real database connection, where the Transaction Synchronization Manager saves the connection of the method bound to the thread before the method invocation, so as to ensure the consistency of the connection throughout the transaction process.

 

 

 

Let's look at the Transaction Synchronization Manager. getResource (Object key) approach

@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doGetResource(actualKey);
    if (value != null && logger.isTraceEnabled()) {
        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
                Thread.currentThread().getName() + "]");
    }
    return value;
}

    @Nullable
private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    Object value = map.get(actualKey);
    // Transparently remove ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

Getting ConnectionHolder from Map of Thread Variables based on DataSource

Existing affairs

As mentioned earlier, the first transaction must start with a new holder and then do the binding operation. At this time, the thread variable has holder and the avtive is true. If the second transaction comes in, the holder is not empty and the activity is true, it will enter the handleExistingTransaction method:

 1 private TransactionStatus handleExistingTransaction(
 2         TransactionDefinition definition, Object transaction, boolean debugEnabled)
 3         throws TransactionException {
 4    // 1.NERVER(Current transactions are not supported;If the current transaction exists, throw an exception)
 5     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
 6         throw new IllegalTransactionStateException(
 7                 "Existing transaction found for transaction marked with propagation 'never'");
 8     }
 9    // 2.NOT_SUPPORTED(Current transaction is not supported, existing synchronization will be suspended),Return an empty transaction
10     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
11         if (debugEnabled) {
12             logger.debug("Suspending current transaction");
13         }
14         // The original transaction will be suspended here.,And return the pending object
15         Object suspendedResources = suspend(transaction);
16         boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
17         // As you can see here, the second parameter transaction Pass an empty transaction, the third parameter false Old label
18         // The last parameter is to encapsulate the previously suspended object into a new one. Status After the current transaction is executed, it is restored suspendedResources
19         return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
20     }
21    // 3.REQUIRES_NEW Suspend the current transaction and create a new transaction
22     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
23         if (debugEnabled) {
24             logger.debug("Suspending current transaction, creating new transaction with name [" +
25                     definition.getName() + "]");
26         }
27         // When the original transaction is suspended, the new transaction is not related to the original transaction.
28         // Will transaction Medium holder Set to null,Then unbind!
29         SuspendedResourcesHolder suspendedResources = suspend(transaction);
30         try {
31             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
32             // new One status Out, in transaction,And mark the new transaction, and then pass in the pending transaction
33             DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
34             // I did it here once. doBegin,At this point transaction in holer It's empty, because the previous business has been suspended.
35             // So here's a new connection and binding!
36             doBegin(transaction, definition);
37             prepareSynchronization(status, definition);
38             return status;
39         }
40         catch (RuntimeException beginEx) {
41             resumeAfterBeginException(transaction, suspendedResources, beginEx);
42             throw beginEx;
43         }
44         catch (Error beginErr) {
45             resumeAfterBeginException(transaction, suspendedResources, beginErr);
46             throw beginErr;
47         }
48     }
49   // If the propagation characteristic at this time is NESTED,Don't hang up
50     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
51         if (!isNestedTransactionAllowed()) {
52             throw new NestedTransactionNotSupportedException(
53                     "Transaction manager does not allow nested transactions by default - " +
54                     "specify 'nestedTransactionAllowed' property with value 'true'");
55         }
56         if (debugEnabled) {
57             logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
58         }
59         // If so JTA Transaction Manager, not available savePoint No, it will not enter this method.
60         if (useSavepointForNestedTransaction()) { 
61             // There will be no pending transactions here. NESTED The feature is just a sub-transaction of the original transaction.
62             // new One status,afferent transaction,Pass in the old transaction tag, pass in the pending object=null
63             DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
64             // Here is NESTED Where the feature is special, a transaction is created in the case of a previous transaction. savePoint
65             status.createAndHoldSavepoint();
66             return status;
67         }
68         else {
69             // JTA Transactions take this branch and create new transactions
70             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
71             DefaultTransactionStatus status = newTransactionStatus(
72                     definition, transaction, true, newSynchronization, debugEnabled, null);
73             doBegin(transaction, definition);
74             prepareSynchronization(status, definition);
75             return status;
76         }
77     }
78 
79     // Come here PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED or PROPAGATION_MANDATORY,If there is a transaction, join the transaction, mark it as an old transaction and hang it empty.
80     boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
81     return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
82 }

 

In the process of dealing with existing transactions, we see many familiar operations, but there are also some differences. There are two cases in the function to consider existing transaction processing.

(1) PROPAGATION_REQUIRES_NEW indicates that the current method must run in its own transaction, a new transaction will be started, and if a transaction is running, it will be suspended during the operation of the method. The biggest difference between Spring's handling of this mode of communication and the establishment of new transactions is that suspend method is used to suspend the original transaction. The purpose of hanging up information is, of course, to restore the original transaction after the current transaction has been executed.

(2) PROPAGATION_NESTED indicates that if a transaction is currently in operation, the method should run in a nested transaction, which can be submitted or rolled back independently of the encapsulated transaction. If the encapsulated transaction does not exist, it behaves like PROPAGATION_REQUIRES_NEW. For embedded transaction processing, Spring mainly considers two ways of processing.

  • When embedded transactions are allowed in Spring, the preferred way to set savepoints is to roll back exception handling.
  • For other ways, such as JTA can't use savepoint, the processing method is the same as PROPAGATION_ REQUIRES_NEW. Once an exception occurs, Spring's transaction exception handling mechanism is used to complete subsequent operations.

The main purpose of suspension operation is to record the state of the original transaction in order to facilitate the recovery of the transaction in subsequent operations.

Summary

Here we can know that in the case of existing transactions, we can decide whether it is a new transaction or whether to suspend the current transaction according to the propagation characteristics.

NOT_SUPPORTED: The transaction is suspended, the doBegin method is not run to pass empty transaction and is marked as an old transaction. Encapsulating status objects:

return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources)

 

REQUIRES_NEW: The transaction is suspended and the doBegin method is run, marked as a new transaction. Encapsulating status objects:

DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

NESTED: No transactions are suspended and no doBegin method is run, marked as an old transaction, but savePoint is created. Encapsulating status objects:

DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);

Other transactions, such as REQUIRED, do not suspend transactions, encapsulate existing transactions, do Begin methods, mark old transactions, and encapsulate status objects:

return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

 

Hang up

The main purpose of suspension operation is to record the status of the original transaction in order to facilitate the recovery of the transaction in subsequent operations:

@Nullable
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
        try {
            Object suspendedResources = null;
            if (transaction != null) {
                // Here's the real way to hang up.Here we return a holder
                suspendedResources = doSuspend(transaction);
            }
            // Here, the name, isolation level, and other information are extracted from the thread variables and the corresponding properties are set to null To thread variables
            String name = TransactionSynchronizationManager.getCurrentTransactionName();
            TransactionSynchronizationManager.setCurrentTransactionName(null);
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
            Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
            boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
            TransactionSynchronizationManager.setActualTransactionActive(false);
            // Hanging transaction attributes to holder Encapsulation together SuspendedResourcesHolder In object
            return new SuspendedResourcesHolder(
                suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
        }
        catch (RuntimeException | Error ex) {
            // doSuspend failed - original transaction is still active...
            doResumeSynchronization(suspendedSynchronizations);
            throw ex;
        }
    }
    else if (transaction != null) {
        // Transaction active but no synchronization active.
        Object suspendedResources = doSuspend(transaction);
        return new SuspendedResourcesHolder(suspendedResources);
    }
    else {
        // Neither transaction nor synchronization active.
        return null;
    }
}

 

@Override
protected Object doSuspend(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // take transaction Medium holder Property set to null
    txObject.setConnectionHolder(null);
    // ConnnectionHolder Unbound from thread variables!
    return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

 

Let's take a look at unbind Resource

private static Object doUnbindResource(Object actualKey) {
    // Gets thread variables for the current thread Map
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    // take key by dataSourece Of value Remove from Map,Then the old one will be replaced by the old one. Holder Return
    Object value = map.remove(actualKey);
    // Remove entire ThreadLocal if empty...
    // If this time map Clear thread variables directly for null
    if (map.isEmpty()) {
        resources.remove();
    }
    // Transparently suppress a ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        value = null;
    }
    if (value != null && logger.isTraceEnabled()) {
        logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
                     Thread.currentThread().getName() + "]");
    }
    // Old Holder Return
    return value;
}

 

You can look back at the introduction of unbinding operations. There are three main things to be done here:

  1. Set the holder attribute in transaction to null
  2. Unbundling (returns the old holder in the thread to be encapsulated in the Suspended ResourcesHolder object)
  3. Put Suspended Resources Holder in status to facilitate the recovery of external transactions after the completion of later sub-transactions

Keywords: Java Attribute JDBC Spring Database

Added by dvwhi on Tue, 15 Oct 2019 06:49:19 +0300