Transaction Interception and Management for Springboot Source Analysis

Summary:

In springboot's automatic assembly transaction, three bean s, Infrastructure Advisor AutoProxy Creator, Transaction Interceptor and Platform Transaction Manager, are assembled. As Infrastructure Advisor AutoProxy Creator has already said, it is a post processor, and its priority is not very high, but the lowest. Today's focus is on the role of the latter two in business. Transaction Interceptor, as a transactional enhancer, plays a central role in enhancing the processing of Spring transactions.

Transaction Interceptor supports the architecture of the whole transaction function, and the logic is relatively complex. Now let's get down to the point and analyze how the interceptor realizes the transaction characteristics.

Three Interfaces of Spring Transaction

Transaction Definition: Describes isolation level, timeout, read-only transactions, and transaction propagation rules

    public interface TransactionDefinition {
        int PROPAGATION_REQUIRED = 0;
        int PROPAGATION_SUPPORTS = 1;
        int PROPAGATION_MANDATORY = 2;
        int PROPAGATION_REQUIRES_NEW = 3;
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
        int ISOLATION_DEFAULT = -1;
        int ISOLATION_READ_UNCOMMITTED = 1;
        int ISOLATION_READ_COMMITTED = 2;
        int ISOLATION_REPEATABLE_READ = 4;
        int ISOLATION_SERIALIZABLE = 8;
        int TIMEOUT_DEFAULT = -1;
    }

Transaction Status: Represents the specific running state of a transaction, as well as the savepoint

    public interface TransactionStatus extends SavepointManager, Flushable {
       // Determine whether the current transaction is a new one
        boolean isNewTransaction();
       // Determine whether the transaction contains a savepoint
        boolean hasSavepoint();
         // This is the only result of the transaction whether to roll back. So if you live outside to try catche and don't let transactions roll back, you'll throw out exceptions that you might see frequently.
        void setRollbackOnly();
    
        boolean isRollbackOnly();
    
        void flush();
       // Whether it's commit or rollback, it's over.~~~
        boolean isCompleted();
    }

It is commonly used to implement the class DefaultTransactionStatus, which is the default transaction state used by Spring.

Platform Transaction Manager: A high-level interface that manages transactions by name

    public interface PlatformTransactionManager {
        TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    
        void commit(TransactionStatus var1) throws TransactionException;
    
        void rollback(TransactionStatus var1) throws TransactionException;
    }

TransactionInterceptor

    public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
        public TransactionInterceptor() {
        }
    
        public TransactionInterceptor(PlatformTransactionManager ptm, Properties attributes) {
            this.setTransactionManager(ptm);
            this.setTransactionAttributes(attributes);
        }
    
        public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) {
            this.setTransactionManager(ptm);
            this.setTransactionAttributeSource(tas);
        }
    
      //The most important way is to intercept the entrance
        @Nullable
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
            Method var10001 = invocation.getMethod();
            invocation.getClass();
            return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
        }
    //Eliminate irrelevant code...
    }

As we already know, it's a Method Interceptor, and the methods intercepted by transactions will eventually be executed on this enhancer.
MethodInterceptor is a circular notification, typing in line with our open, submit, rollback transactions and other operations, source code analysis can be seen that the real thing is still in the parent class, it has a template to execute transactions.

TransactionAspectSupport

    public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
        private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
        // The current TransactionStatus () method needs to use it
        private static final ThreadLocal<TransactionAspectSupport.TransactionInfo> transactionInfoHolder = new NamedThreadLocal("Current aspect-driven transaction");
        protected final Log logger = LogFactory.getLog(this.getClass());
        //Name of transaction manager
        @Nullable
        private String transactionManagerBeanName;
        //Transaction Manager
        @Nullable
        private PlatformTransactionManager transactionManager;
        //Transaction attribute source
        @Nullable
        private TransactionAttributeSource transactionAttributeSource;
        @Nullable
        private BeanFactory beanFactory;
        // Because the transaction manager may have more than one, a simple cache is made here.~
        private final ConcurrentMap<Object, PlatformTransactionManager> transactionManagerCache = new ConcurrentReferenceHashMap(4);
    
        public TransactionAspectSupport() {
        }
    
        @Nullable
        protected static TransactionAspectSupport.TransactionInfo currentTransactionInfo() throws NoTransactionException {
            return (TransactionAspectSupport.TransactionInfo)transactionInfoHolder.get();
        }
        //This Static method is called externally to obtain the status of the current transaction and even to submit and roll back the transaction manually.
        public static TransactionStatus currentTransactionStatus() throws NoTransactionException {
            TransactionAspectSupport.TransactionInfo info = currentTransactionInfo();
            if (info != null && info.transactionStatus != null) {
                return info.transactionStatus;
            } else {
                throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope");
            }
        }
       //Eliminate irrelevant code...
      // It can be found here that if the incoming property is actually matched by NameMatch Transaction Attribute Source, the transaction Attribute Source will be overwritten.
        public void setTransactionAttributes(Properties transactionAttributes) {
            NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
            tas.setProperties(transactionAttributes);
            this.transactionAttributeSource = tas;
        }
        // Choose according to method and target class
        public void setTransactionAttributeSources(TransactionAttributeSource... transactionAttributeSources) {
            this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
        }
       //Eliminate irrelevant code...
      // Next up is our core template approach to transaction processing.
       @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.
        // Getting the Transaction Attribute Source~
            TransactionAttributeSource tas = getTransactionAttributeSource();
        // Get the transaction attributes corresponding to this method (this is particularly important)
       // Different transaction modes use different logic. For declarative transaction processing and programmatic transaction processing, the important difference lies in transaction attributes, because programmatic transaction processing does not need transaction attributes.
            final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        // Find a suitable transaction manager
            final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        // Get the unique identification of the target method
            final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    
            if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
                // Standard transaction demarcation with getTransaction and commit/rollback calls.
          // See if it is necessary to create a transaction and make a corresponding judgment based on the `Transaction Communication Behavior'.
                TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
                Object retVal;
                try {
                    // This is an around advice: Invoke the next interceptor in the chain.
                    // This will normally result in a target object being invoked.
            //Callback method execution, target method execution (original business logic)
                    retVal = invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                    // target invocation exception
            // If an exception occurs, roll back (note: not all exceptions roll back)
                    // Note: If there are no transaction attributes, commit will be compatible with programmable transactions.
                    completeTransactionAfterThrowing(txInfo, ex);
                    throw ex;
                }
                finally {
            //Clear up information
                    cleanupTransactionInfo(txInfo);
                }
          // When the target method is fully executed, commit the transaction~~~
                commitTransactionAfterReturning(txInfo);
                return retVal;
            }
            else {
          //Callback Preferring Platform Transaction Manager will be here 
            // The principle is not too bad. I will not elaborate on it here.~~~~
        
                final ThrowableHolder throwableHolder = new ThrowableHolder();
    
                // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
                try {
                    Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                        TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                        try {
                            return invocation.proceedWithInvocation();
                        }
                        catch (Throwable ex) {
                            if (txAttr.rollbackOn(ex)) {
                                // A RuntimeException: will lead to a rollback.
                                if (ex instanceof RuntimeException) {
                                    throw (RuntimeException) ex;
                                }
                                else {
                                    throw 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;
                }
            }
        }
      // Find a transaction manager from the container
        @Nullable
        protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
            if (txAttr != null && this.beanFactory != null) {
              // That's where qualifier works. He's the equivalent of BeanName.
                String qualifier = txAttr.getQualifier();
                if (StringUtils.hasText(qualifier)) {
                  // According to this name and Platform Transaction Manager. class, go to the container to find it.
                    return this.determineQualifiedTransactionManager(this.beanFactory, qualifier);  // If qualifier is not specified, see if transactionManagerBeanName is specified
                } else if (StringUtils.hasText(this.transactionManagerBeanName)) {
                  
                    return this.determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
                } else {
                  // If it's not designated, it doesn't matter. Look for getBean(Class) in containers directly by type
            // Here: If there are two Platform Transaction Managers in the container, you're bound to report errors.~~~
        
                    PlatformTransactionManager defaultTransactionManager = this.getTransactionManager();
                    if (defaultTransactionManager == null) {
                        defaultTransactionManager = (PlatformTransactionManager)this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
                        if (defaultTransactionManager == null) {
                            defaultTransactionManager = (PlatformTransactionManager)this.beanFactory.getBean(PlatformTransactionManager.class);
                            this.transactionManagerCache.putIfAbsent(DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
                        }
                    }
    
                    return defaultTransactionManager;
                }
            } else {
              // If neither of them is configured, so you must have set up Platform Transaction Manager manually, you can go back directly.
                return this.getTransactionManager();
            }
        }
    
        private PlatformTransactionManager determineQualifiedTransactionManager(BeanFactory beanFactory, String qualifier) {
            PlatformTransactionManager txManager = (PlatformTransactionManager)this.transactionManagerCache.get(qualifier);
            if (txManager == null) {
                txManager = (PlatformTransactionManager)BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, PlatformTransactionManager.class, qualifier);
                this.transactionManagerCache.putIfAbsent(qualifier, txManager);
            }
    
            return txManager;
        }
    
        private String methodIdentification(Method method, @Nullable Class<?> targetClass, @Nullable TransactionAttribute txAttr) {
            String methodIdentification = this.methodIdentification(method, targetClass);
            if (methodIdentification == null) {
                if (txAttr instanceof DefaultTransactionAttribute) {
                    methodIdentification = ((DefaultTransactionAttribute)txAttr).getDescriptor();
                }
    
                if (methodIdentification == null) {
                    methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
                }
            }
    
            return methodIdentification;
        }
    
        @Nullable
        protected String methodIdentification(Method method, @Nullable Class<?> targetClass) {
            return null;
        }
    // If necessary, create a Transaction Info (specific transactions from getTransaction () in the transaction manager)
        protected TransactionAspectSupport.TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
          //assignment
            if (txAttr != null && ((TransactionAttribute)txAttr).getName() == null) {
                txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
                    public String getName() {
                        return joinpointIdentification;
                    }
                };
            }
    // From the transaction manager, pull out a Transaction Status through txAttr
            TransactionStatus status = null;
            if (txAttr != null) {
                if (tm != null) {
                    status = tm.getTransaction((TransactionDefinition)txAttr);
                } else if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
                }
            }
    // Transform to a generic Transaction Info through Transaction Statues, etc.
            return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
        }
        protected TransactionAspectSupport.TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, String joinpointIdentification, @Nullable TransactionStatus status) {
          //Construct a Transaction Info
            TransactionAspectSupport.TransactionInfo txInfo = new TransactionAspectSupport.TransactionInfo(tm, txAttr, joinpointIdentification);
            if (txAttr != null) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
                }
               // Setting Transaction Status
                txInfo.newTransactionStatus(status);
            } else if (this.logger.isTraceEnabled()) {
                this.logger.trace("No need to create transaction for [" + joinpointIdentification + "]: This method is not transactional.");
            }
    // This sentence is the most important, binding the generated Transaction Info to the ThreadLocal of the current thread
            txInfo.bindToThread();
            return txInfo;
        }
    //It is relatively simple to submit transactions only with transaction manager ~~concrete implementation logic in the common implementation of the transaction manager ~~
        protected void commitTransactionAfterReturning(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
            if (txInfo != null && txInfo.getTransactionStatus() != null) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
                }
    
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
    
        }
    
        protected void completeTransactionAfterThrowing(@Nullable TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {
            if (txInfo != null && txInfo.getTransactionStatus() != null) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);
                }
    // If there are transaction attributes, call rollbackOn to see if the exception does not need to be rolled back.
                if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                    try {
                        txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                    } catch (TransactionSystemException var6) {
                        this.logger.error("Application exception overridden by rollback exception", ex);
                        var6.initApplicationException(ex);
                        throw var6;
                    } catch (Error | RuntimeException var7) {
                        this.logger.error("Application exception overridden by rollback exception", ex);
                        throw var7;
                    }
                } else {
                  // Programming transactions do not have transaction attributes, so commit
                    try {
                        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                    } catch (TransactionSystemException var4) {
                        this.logger.error("Application exception overridden by commit exception", ex);
                        var4.initApplicationException(ex);
                        throw var4;
                    } catch (Error | RuntimeException var5) {
                        this.logger.error("Application exception overridden by commit exception", ex);
                        throw var5;
                    }
                }
            }
    
        }
    
        protected void cleanupTransactionInfo(@Nullable TransactionAspectSupport.TransactionInfo txInfo) {
            if (txInfo != null) {
                txInfo.restoreThreadLocalStatus();
            }
    
        }
    
        private static class ThrowableHolderException extends RuntimeException {
            public ThrowableHolderException(Throwable throwable) {
                super(throwable);
            }
    
            public String toString() {
                return this.getCause().toString();
            }
        }
    
        private static class ThrowableHolder {
            @Nullable
            public Throwable throwable;
    
            private ThrowableHolder() {
            }
        }
    
        @FunctionalInterface
        protected interface InvocationCallback {
            Object proceedWithInvocation() throws Throwable;
        }
    
        protected final class TransactionInfo {
          // Transaction Manager for Current Transactions
            @Nullable
            private final PlatformTransactionManager transactionManager;
          // Transaction attributes of current transactions
            @Nullable
            private final TransactionAttribute transactionAttribute;
          //Joint point identity
            private final String joinpointIdentification;
          //TransactionStatus of Current Transactions
            @Nullable
            private TransactionStatus transactionStatus;
          // The key is the old Transaction Info field.
              // This field preserves references to the `parent transaction'context of the current transaction, forming a chain, or, to be exact, a directed acyclic graph.
        
            @Nullable
            private TransactionAspectSupport.TransactionInfo oldTransactionInfo;
    
            public TransactionInfo(@Nullable PlatformTransactionManager transactionManager, @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
                this.transactionManager = transactionManager;
                this.transactionAttribute = transactionAttribute;
                this.joinpointIdentification = joinpointIdentification;
            }
    
            public PlatformTransactionManager getTransactionManager() {
                Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
                return this.transactionManager;
            }
    
            @Nullable
            public TransactionAttribute getTransactionAttribute() {
                return this.transactionAttribute;
            }
    
            public String getJoinpointIdentification() {
                return this.joinpointIdentification;
            }
            //Notice the method name, a new transaction status
            public void newTransactionStatus(@Nullable TransactionStatus status) {
                this.transactionStatus = status;
            }
    
            @Nullable
            public TransactionStatus getTransactionStatus() {
                return this.transactionStatus;
            }
    
            public boolean hasTransaction() {
                return this.transactionStatus != null;
            }
             //Bind all information about the current transaction being processed to ThreadLocal
            private void bindToThread() {
              // Old transactions are taken out of threads and bound to new (or current) transactions.~~~~~~
                this.oldTransactionInfo = (TransactionAspectSupport.TransactionInfo)TransactionAspectSupport.transactionInfoHolder.get();
                TransactionAspectSupport.transactionInfoHolder.set(this);
            }
            //After the current transaction has been processed, the parent transaction context is restored
            private void restoreThreadLocalStatus() {
                TransactionAspectSupport.transactionInfoHolder.set(this.oldTransactionInfo);
            }
    
            public String toString() {
                return this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction";
            }
        }
    }

Transaction Manager

AbstractPlatformTransactionManager

It is an abstract implementation of Platform Transaction Manager. Implementing Spring's Standard Transaction Workflow
This base class provides the following workflow processing:

  • Determine if there are existing transactions;
  • Applying appropriate communication behavior;
  • If necessary, suspend and resume business;
  • Check rollback-only tags on submission;
  • Apply appropriate modifications when rollback (actual rollback or rollback-only setting);
    Trigger synchronous callback registration (if transaction synchronization is activated)
    public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
    
        //Always activate transaction synchronization (see Transaction propagation properties ~)
        public static final int SYNCHRONIZATION_ALWAYS = 0;
        //Activate transaction synchronization only for actual transactions (i.e., not for empty transactions caused by propagation)\Do not support existing back-end transactions
        public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
        //Never activate transaction synchronization
        public static final int SYNCHRONIZATION_NEVER = 2;
    
        // The equivalent of collecting all public static final variables of this class here~~~~
        private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class);
    
        // =========== Default values
        private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
        // The default transaction timeout is - 1 to indicate no timeout
        private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT;
        //Set whether nested transactions are allowed. Default is "false".
        private boolean nestedTransactionAllowed = false;
        // Set whether existing transactions should be validated before participating
        private boolean validateExistingTransaction = false;
        
        //Attention should be paid to setting whether to mark an existing transaction `global'as a rollback default value only after the participating transaction `fails'.~~~
        // Indicates that whenever your transaction fails, mark it rollback-only to indicate that it can only roll back and not commit or end normally.
        // One of the mistakes that this caller often makes is that the upper transaction service throws an exception and gives it to try by itself, without throwing it, it will surely make a mistake:
        // Error message: Transaction rolled back because it has been marked as rollback-only
        // Of course, this property forbids setting it to false.~~~~~~
        private boolean globalRollbackOnParticipationFailure = true;
        // If the transaction is globally marked as rollback only, set whether it fails early~~~~
        private boolean failEarlyOnGlobalRollbackOnly = false;
        // Setting whether @code dorollback should be executed when the @code docommit call fails is usually not required and should therefore be avoided
        private boolean rollbackOnCommitFailure = false;
        
        // We find it a bit enumerative, especially when configuring with XML, it's very much like enumeration.~~~~~~~
        // This is also the significance of Constants.~~~~
        public final void setTransactionSynchronizationName(String constantName) {
            setTransactionSynchronization(constants.asNumber(constantName).intValue());
        }
        public final void setTransactionSynchronization(int transactionSynchronization) {
            this.transactionSynchronization = transactionSynchronization;
        }
        //... omit some get/set methods for all fields above~~~
    
        // The most important method is to obtain a transaction TransactionStatus according to the physical definition. 
        @Override
        public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
            //The doGetTransaction() method is an abstract method, and the concrete implementation is provided by a specific transaction processor (for example, DataSource Transaction Manager)
            Object transaction = doGetTransaction();
    
            //If no transaction properties are configured, the default transaction properties are used
            if (definition == null) {
                definition = new DefaultTransactionDefinition();
            }
    
            //Check whether there is a transaction isExistingTransaction in the current thread. This method returns false by default, but subclasses override this method.
            if (isExistingTransaction(transaction)) {
                // The handleExistingTransaction method handles existing transactions
                // The implementation of this method is also very complicated. In short, some propagation attributes are parsed. Considerations of various situations ~~~will be invoked if a new transaction generates doBegin().~~~~
                return handleExistingTransaction(definition, transaction, debugEnabled);
            }
    
            // Simple Check of Overtime~~~~
            if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
                throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
            }
    
            // Transaction propagation characteristics configured in transaction attributes==============
        
            // PROPAGATION_MANDATORY supports the current transaction if a transaction already exists. If there is no active transaction, an exception is thrown
            if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
                throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
            }
        
            //If the transaction propagation characteristics are required, required_new, or nested
            else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                    definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                    definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
                    
                // Suspend, but doSuspend() is implemented by subclasses~~~
                // Suspended operations trigger related pended registered events, encapsulate all attributes of current threaded items, and place them in a Suspended Resources Holder
                // Then clear out the current thread transaction`
                SuspendedResourcesHolder suspendedResources = suspend(null);
    
                // Here, start creating transactions~~~~~
                try {
                    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    
                    // // Creating a new transaction state is new DefaultTransactionStatus() assigning all attributes to
                    DefaultTransactionStatus status = newTransactionStatus(
                            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                    // Start a transaction, Abstract methods, implemented by subclasses~
                    doBegin(transaction, definition);
                    //Initialization and synchronization transaction state is the Transaction Synchronization Manager class, which maintains a lot of ThreadLocal internally.
                    prepareSynchronization(status, definition);
                    return status;
                }
                catch (RuntimeException | Error ex) {
                    //Restart doResume to be implemented by subclasses
                    resume(null, suspendedResources);
                    throw ex;
                }
            }
            // To propagate attributes here is to create one directly without the need for transactions.
            else {
                boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
                // This method is equivalent to two steps: first, new Transaction Status, and then prepareSynchronization.~~~
                // The obvious difference is that the call to doBegin() method is not inserted back in the middle, because there is no transaction begin.~~
                return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
            }
        }
    
    
        // Look at the commit method again
        @Override
        public final void commit(TransactionStatus status) throws TransactionException {
            //If it's a completed thing, it can't be submitted repeatedly.
            if (status.isCompleted()) {
                throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
            }
    
            DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
            // If it has been marked for rollback, then perform rollback
            if (defStatus.isLocalRollbackOnly()) {
                processRollback(defStatus, false);
                return;
            }
    
            //  The default value shouldCommitOnGlobalRollbackOnly is false. Currently, only JTA transactions are copied as true.
            // IsGlobal RollbackOnly: Is it marked as global RolbackOnly
            if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
                processRollback(defStatus, true);
                return;
            }
            // Submitting a transaction is still complicated, taking into account the restore point, the new transaction, whether the transaction is rollback-only, and so on.~~
            processCommit(defStatus);
        }
    
        // The doRollback method in the rollback method is handed over to subclasses for implementation.~~~
        @Override
        public final void rollback(TransactionStatus status) throws TransactionException {
            DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
            processRollback(defStatus, false);
        }
    }

From the analysis of the abstract class source code, we can see that it is absolutely a very typical template implementation, which is the case with all methods. I first provide the implementation template, many specific implementation schemes are open to subclasses, such as start, suspend, resume, commit, rollback, etc., which is equivalent to leaving a lot of connection points.

DataSourceTransactionManager

    // It also implements the ResourceTransaction Manager interface and provides the getResourceFactory() method
    public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {
        // Obviously it manages DataSource and JTA Distributed Transaction Management may be a variety of data sources.
        @Nullable
        private DataSource dataSource;
        // Do not force the marker ReadOnly
        private boolean enforceReadOnly = false;
    
        // JDBC allows embedded transactions by default
        public DataSourceTransactionManager() {
            setNestedTransactionAllowed(true);
        }
        public DataSourceTransactionManager(DataSource dataSource) {
            this();
            setDataSource(dataSource);
            // Its own Initializing Bean also did a simple check.~~~
            afterPropertiesSet();
        }
    
        // Manual Setting of Data Sources
        public void setDataSource(@Nullable DataSource dataSource) {
            // This step is necessary.
            // Transaction Aware Data Source Proxy is the packaging of data Source
            if (dataSource instanceof TransactionAwareDataSourceProxy) {
                this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
            } else {
                this.dataSource = dataSource;
            }
        }
    
        //Return the JDBC DataSource
        @Nullable
        public DataSource getDataSource() {
            return this.dataSource;
        }
        // @ Since 5.0 Spring 5.0 provides a method that is actually empty by calling getDataSource().
        protected DataSource obtainDataSource() {
            DataSource dataSource = getDataSource();
            Assert.state(dataSource != null, "No DataSource set");
            return dataSource;
        }
        // Data source returned directly~~~~
        @Override
        public Object getResourceFactory() {
            return obtainDataSource();
        }
        ...
        // Here returns a `Data Source Transaction Object'.`
        // It's a `JdbcTransaction Object Support', so it's Savepoint Manager and implements the Smart Transaction Object interface.
        @Override
        protected Object doGetTransaction() {
            DataSourceTransactionObject txObject = new DataSourceTransactionObject();
            txObject.setSavepointAllowed(isNestedTransactionAllowed());
            // This acquisition is interesting ~~equivalent to following threads~~~
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
            txObject.setConnectionHolder(conHolder, false);
            return txObject;
        }
    
        // Check whether the current transaction is active
        @Override
        protected boolean isExistingTransaction(Object transaction) {
            DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
            return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
        }
    
    
        // This is a core content, which needs analysis and analysis of logic.~~~
        @Override
        protected void doBegin(Object transaction, TransactionDefinition definition) {
            DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
            Connection con = null;
    
            try {
                if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                    // Get a connection from a DataSource (this DataSource usually has a connection pool ~)
                    Connection newCon = obtainDataSource().getConnection();
                    // Wrap this link in Connection Holder~~~
                    txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
                }
    
                txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
                con = txObject.getConnectionHolder().getConnection();
                
                // Set isReadOnly, isolation boundaries, etc.~
                Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
                txObject.setPreviousIsolationLevel(previousIsolationLevel);
    
                // The key here is to see if Connection is submitted automatically.
                // If it's con.setAutoCommit(false), otherwise the database defaults to not execute an SQL is a transaction, you can't manage the transaction.
                if (con.getAutoCommit()) {
                    txObject.setMustRestoreAutoCommit(true);
                    con.setAutoCommit(false);
                }
                // ==== So from here on, all the SQL statements executed through this Connection will not be submitted to the database as long as there is no commit=====
                
                // This method is particularly interesting because it gets a Statement by itself `Statement stmt = con.createStatement()'.
                // Then execute an SQL: `stmt.executeUpdate("SET TRANSACTION READ ONLY");`
                // So, so: if you just query. Setting the transaction property to readonly = true Spring helps you optimize SQl
                // Note that after readonly=true, you can only read, not dml operation. (You can only see the change of data before setting things, but not after setting things.)
                prepareTransactionalConnection(con, definition);
                txObject.getConnectionHolder().setTransactionActive(true);
    
                int timeout = determineTimeout(definition);
                if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
                }
    
                // Bind the connection holder to the thread.
                // This step: bind the current link to the current thread~~~~
                if (txObject.isNewConnectionHolder()) {
                    TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
                }
            } catch (Throwable ex) {
                // If it's a newly created link, release it~~~~
                if (txObject.isNewConnectionHolder()) {
                    DataSourceUtils.releaseConnection(con, obtainDataSource());
                    txObject.setConnectionHolder(null, false);
                }
                throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
            }
        }
    
        // True Submission
        @Override
        protected void doCommit(DefaultTransactionStatus status) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
            // Get the link and commit it directly.   
            Connection con = txObject.getConnectionHolder().getConnection();
            try {
                con.commit();
            } catch (SQLException ex) {
                throw new TransactionSystemException("Could not commit JDBC transaction", ex);
            }
        }
        //The doRollback() method is similar to that described here.
    }

Summary:

Transaction property readonly=true, only read operation (only see the change of data before setting things, not the change of data after setting things) But through the source code, I found that you only set @Transactional (readonly=true) is not enough, and you have to configure DataSource Transaction Manager to do this. Data Source Transaction Manager. setEnforceReadOnly (true) will ultimately optimize your read-only transactions.~~~~
In fact, if you just set @Transactional(readOnly = true), you will eventually set this Connection to read-only: con.setReadOnly(true); it means that the connection is set to read-only mode as a hint for the driver to enable database optimization. After setting the link to read-only mode to inform the database, the database will do its own read-only optimization. However, this is not necessarily a readonly transaction for a database, which is very important. (Because, after all, there may be multiple links within a transaction.

Keywords: Java Database Spring Attribute JDBC

Added by chuckym7 on Fri, 30 Aug 2019 16:44:49 +0300