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:
- Set the holder attribute in transaction to null
- Unbundling (returns the old holder in the thread to be encapsulated in the Suspended ResourcesHolder object)
- Put Suspended Resources Holder in status to facilitate the recovery of external transactions after the completion of later sub-transactions