spring transaction source code

Posted by hey_suburbia on Thu, 03 Feb 2022 07:03:01 +0100

 

 
 

 

text

Series catalog

Detailed explanation of spring transactions (I) preliminary exploration

Detailed explanation of spring transaction (II) simple example

spring transaction explanation (III) source code explanation

Detailed explanation of spring transactions (IV) test and verification

Detailed explanation of spring Affairs (V) summary and improvement

1, Introduction

In Spring, transactions can be implemented in two ways:

  1. Programming transaction management: using TransactionTemplate in programming transaction management can realize more fine-grained transaction control.
  2. Declarative transaction management: Based on Spring AOP implementation. Its essence is to intercept before and after the method, then create or join a transaction before the start of the target method, and commit or roll back the transaction according to the execution after the execution of the target method.

Declarative transaction management does not need intrusion code. Transaction operations can be carried out through @ Transactional, which is faster and simpler (especially with spring boot automatic configuration, it can be said to be extremely streamlined!), And most businesses can be met. It is recommended to use.

In fact, whether it is a programmatic transaction or a declarative transaction, the underlying core code of the final call is the same. This chapter starts with programming and declaration, and then enters the penetrating explanation of the core source code.

2, Transaction source code

2.1 programmatic transaction template

For programming transactions, Spring has provided us with the template class TransactionTemplate, which can be used conveniently, as shown in the following figure:

The full pathname of the transactiontemplate is: org springframework. transaction. support. TransactionTemplate. Look at the package name. This is the Template class of spring for transactions. (spring is all kinds of templates...), Look at the class diagram below:

At first glance, yo Xi has implemented two interfaces: TransactionOperations and InitializingBean (those familiar with spring source code know that this InitializingBean is an old routine). Let's look at the interface source code as follows:

 1 public interface TransactionOperations {
 2 
 3     /**
 4      * Execute the action specified by the given callback object within a transaction.
 5      * <p>Allows for returning a result object created within the transaction, that is,
 6      * a domain object or a collection of domain objects. A RuntimeException thrown
 7      * by the callback is treated as a fatal exception that enforces a rollback.
 8      * Such an exception gets propagated to the caller of the template.
 9      * @param action the callback object that specifies the transactional action
10      * @return a result object returned by the callback, or {@code null} if none
11      * @throws TransactionException in case of initialization, rollback, or system errors
12      * @throws RuntimeException if thrown by the TransactionCallback
13      */
14     <T> T execute(TransactionCallback<T> action) throws TransactionException;
15 
16 }
17 
18 public interface InitializingBean {
19 
20     /**
21      * Invoked by a BeanFactory after it has set all bean properties supplied
22      * (and satisfied BeanFactoryAware and ApplicationContextAware).
23      * <p>This method allows the bean instance to perform initialization only
24      * possible when all bean properties have been set and to throw an
25      * exception in the event of misconfiguration.
26      * @throws Exception in the event of misconfiguration (such
27      * as failure to set an essential property) or if initialization fails.
28      */
29     void afterPropertiesSet() throws Exception;
30 
31 }

As shown in the figure above, the TransactionOperations interface is used to execute the callback method of transactions. InitializingBean is a typical spring bean initialization process (air ticket: Spring IOC (IV) summary and sublimation )The reserved interface of is specially used to execute the method when the bean properties are loaded.

Back to the point, what does the impl method of the two interfaces of TransactionTemplate do?

 1     @Override
 2     public void afterPropertiesSet() {
 3         if (this.transactionManager == null) {
 4             throw new IllegalArgumentException("Property 'transactionManager' is required");
 5         }
 6     }
 7 
 8 
 9     @Override
10     public <T> T execute(TransactionCallback<T> action) throws TransactionException {
       // Internally encapsulated transaction manager 11 if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { 12 return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); 13 }// You need to manually obtain the transaction, execute the method, and commit the transaction manager 14 else {// 1.Get transaction status 15 TransactionStatus status = this.transactionManager.getTransaction(this); 16 T result; 17 try {// 2.Execute business logic 18 result = action.doInTransaction(status); 19 } 20 catch (RuntimeException ex) { 21 // Application runtime exception -> RollBACK 22 rollbackOnException(status, ex); 23 throw ex; 24 } 25 catch (Error err) { 26 // Error abnormal -> RollBACK 27 rollbackOnException(status, err); 28 throw err; 29 } 30 catch (Throwable ex) { 31 // Unknown exception -> RollBACK 32 rollbackOnException(status, ex); 33 throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); 34 }// 3.Transaction commit 35 this.transactionManager.commit(status); 36 return result; 37 } 38 }

As shown in the above figure, in fact, afterpropertieset only verifies that the transaction manager is not empty, and execute() is the core method. The main steps of execute are as follows:

1.getTransaction() gets the transaction. See 3.3.1 for the source code

2.doInTransaction() executes business logic. Here is the user-defined business code. If there is no return value, it is doInTransactionWithoutResult().

3.commit() transaction commit: call the commit of AbstractPlatformTransactionManager, rollback onexception() exception rollback: call the rollback() of AbstractPlatformTransactionManager, transaction commit rollback, see 3.3.3 for the source code

 

2.2 declarative transaction @ Transactional

1.AOP related concepts

Declarative transactions use spring AOP, which is aspect oriented programming. (what ❓ You don't know what AOP is In a word, make the repeated code in the business code into a facet, extract it, and define which methods need to execute this facet. Other self Baidu Bar...) The core concepts of AOP are as follows:

  • Advice: it defines the aspect (a aspect refined from the logic required in all business codes) what to do and when to use what+when. For example: pre notification Before, post notification After, return notification After returning, exception notification After throwing, surround notification
  • Joint point: the point that can be inserted into the section during the execution of the program. Generally, there are multiple points. For example, when calling a method or throwing an exception.
  • Pointcut: a pointcut defines a connection point. A pointcut contains multiple connection points, i.e. where and where to use notifications Usually specify class + method or regular expression to match class and method names.
  • Aspect: aspect = notification + pointcut, i.e. when+where+what, when and where.
  • Introduction: allows us to add new methods or properties to existing classes.
  • Weaving: weaving is the process of applying facets to the target object and creating a new proxy object.

2. Declarative affairs

The declarative transaction calls the process as a whole, and two lines can be drawn:

1. Use proxy mode to generate proxy enhancement classes.

2. According to the agent transaction management configuration class, configure the weaving of transactions, enhance the surrounding before and after the business method, and add some transaction related operations. For example, get transaction properties, commit transactions, and roll back transactions.

The process is as follows:

Declarative transactions use @ Transactional annotation, so we load them from the automatic configuration when the spring boot container starts( spring boot container startup details )Start watching. At / meta-inf / spring Find in the configuration file in factories, as shown in the following figure:

 

Load 2 automatic configuration classes about transactions:

org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,

jta let's not look at it. Let's take a look at the automatic configuration class of TransactionAutoConfiguration:

 1 @Configuration
 2 @ConditionalOnClass(PlatformTransactionManager.class)
 3 @AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
 4         DataSourceTransactionManagerAutoConfiguration.class,
 5         Neo4jDataAutoConfiguration.class })
 6 @EnableConfigurationProperties(TransactionProperties.class)
 7 public class TransactionAutoConfiguration {
 8 
 9     @Bean
10     @ConditionalOnMissingBean
11     public TransactionManagerCustomizers platformTransactionManagerCustomizers(
12             ObjectProvider<List<PlatformTransactionManagerCustomizer<?>>> customizers) {
13         return new TransactionManagerCustomizers(customizers.getIfAvailable());
14     }
15 
16     @Configuration
17     @ConditionalOnSingleCandidate(PlatformTransactionManager.class)
18     public static class TransactionTemplateConfiguration {
19 
20         private final PlatformTransactionManager transactionManager;
21 
22         public TransactionTemplateConfiguration(
23                 PlatformTransactionManager transactionManager) {
24             this.transactionManager = transactionManager;
25         }
26 
27         @Bean
28         @ConditionalOnMissingBean
29         public TransactionTemplate transactionTemplate() {
30             return new TransactionTemplate(this.transactionManager);
31         }
32     }
33 
34     @Configuration
35     @ConditionalOnBean(PlatformTransactionManager.class)
36     @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
37     public static class EnableTransactionManagementConfiguration {
38 
39         @Configuration
40         @EnableTransactionManagement(proxyTargetClass = false)
41         @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
42         public static class JdkDynamicAutoProxyConfiguration {
43 
44         }
45 
46         @Configuration
47         @EnableTransactionManagement(proxyTargetClass = true)
48         @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
49         public static class CglibAutoProxyConfiguration {
50 
51         }
52 
53     }
54 
55 }

TransactionAutoConfiguration mainly focuses on:

1.2 class annotations

@ConditionalOnClass(PlatformTransactionManager.class) means that the automatic configuration takes effect when the PlatformTransactionManager class is included in the class path. This class is the core package of spring transactions and must be introduced.

@Autoconfigurareafter ({jtaautoconfiguration. Class, hibernatejpaautoconfiguration. Class, datasourcetransactionmanagerautoconfiguration. Class, neo4jdataautoconfiguration. Class}), this configuration takes effect only after the four configuration classes in brackets.

2. 2 internal classes

TransactionTemplateConfiguration transaction template configuration class:

@ConditionalOnSingleCandidate(PlatformTransactionManager.class) takes effect only when a PlatformTransactionManager bean can be uniquely identified.

@ConditionalOnMissingBean if no TransactionTemplate bean is defined, generate one.

EnableTransactionManagementConfiguration enable transaction manager configuration class:

@ConditionalOnBean(PlatformTransactionManager.class) takes effect when there is a PlatformTransactionManager bean.

@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class) takes effect only when there is no custom Abstract transaction manager configuration class. (that is, the user-defined Abstract transaction manager configuration class will take precedence. If not, use this default transaction manager configuration class)

EnableTransactionManagementConfiguration supports two proxy methods:

  • 1.JdkDynamicAutoProxyConfiguration:

@EnableTransactionManagement(proxyTargetClass = false), that is, proxyTargetClass = false indicates that it is a JDK dynamic agent. It supports interface oriented agent.

@Conditionalonproperty (prefix = "spring. AOP", name = "proxy target class", havingvalue = "false", matchifmissing = false), i.e. spring aop. It will take effect when proxy target class = false, and it will not take effect without this configuration.

  • 2.CglibAutoProxyConfiguration:

@EnableTransactionManagement(proxyTargetClass = true), that is, proxyTargetClass = true identifies that Cglib agent supports subclass inheritance agent.
@Conditionalonproperty (prefix = "spring. AOP", name = "proxy target class", havingvalue = "true", matchifmissing = true), i.e. spring aop. It takes effect when proxy target class = true, and if there is no such configuration, it takes effect by default.

Note that there is no configuration by default and the Cglib agent is used. Note: the @ Transactional annotation supports adding directly to the class.

Well, after reading so many configuration classes, I finally came to the annotation @ EnableTransactionManagement.

 1 @Target(ElementType.TYPE)
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Import(TransactionManagementConfigurationSelector.class)
 5 public @interface EnableTransactionManagement {
 6 
 7     //proxyTargetClass = false Say yes JDK Dynamic proxy supports interface proxy. true Say yes Cglib Proxy supports subclass inheritance proxy.
 8     boolean proxyTargetClass() default false;
 9 
10     //Transaction notification mode (aspect weaving mode), default proxy mode (interceptors calling each other in the same class will not take effect), and enhanced AspectJ can be selected
11     AdviceMode mode() default AdviceMode.PROXY;
12 
13     //When there are multiple notifications on the connection point, they are sorted. The default is the lowest. The higher the value, the lower the priority.
14     int order() default Ordered.LOWEST_PRECEDENCE;
15 
16 }

Focus on the class annotation @ Import(TransactionManagementConfigurationSelector.class)

The class diagram of TransactionManagementConfigurationSelector is as follows:

As shown in the figure above, the TransactionManagementConfigurationSelector inherits from the AdviceModeImportSelector and implements the ImportSelector interface.

 1 public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
 2 
 3     /**
 4      * {@inheritDoc}
 5      * @return {@link ProxyTransactionManagementConfiguration} or
 6      * {@code AspectJTransactionManagementConfiguration} for {@code PROXY} and
 7      * {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, respectively
 8      */
 9     @Override
10     protected String[] selectImports(AdviceMode adviceMode) {
11         switch (adviceMode) {
12             case PROXY:
13                 return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
14             case ASPECTJ:
15                 return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
16             default:
17                 return null;
18         }
19     }
20 
21 }

As shown in the figure above, the selectImports method will eventually be executed to import the classes to be loaded. We only see that in the proxy mode, two classes, autoproxyregister and proxytransactionmanagementconfigurationare loaded.

  • Autoproxyregister: register an infrastructure advisor autoproxycreator component in the container; The post processor mechanism is used to wrap the object and return a proxy object (enhancer) after the object is created. The proxy object execution method is called by the interceptor chain;
  • ProxyTransactionManagementConfiguration: a configuration class that defines a transaction enhancer.

AutoProxyRegistrar

First, autoproxyregister implements the importbeandefinitionregister interface and replicates the registerBeanDefinitions method. The source code is as follows:

 1 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 2         boolean candidateFound = false;
 3         Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
 4         for (String annoType : annoTypes) {
 5             AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
 6             if (candidate == null) {
 7                 continue;
 8             }
 9             Object mode = candidate.get("mode");
10             Object proxyTargetClass = candidate.get("proxyTargetClass");
11             if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
12                     Boolean.class == proxyTargetClass.getClass()) {
13                 candidateFound = true;
14                 if (mode == AdviceMode.PROXY) {//proxy pattern
15                     AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
16                     if ((Boolean) proxyTargetClass) {//If it is CGLOB Subclass proxy mode
17                         AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
18                         return;
19                     }
20                 }
21             }
22         }
23         if (!candidateFound) {
24             String name = getClass().getSimpleName();
25             logger.warn(String.format("%s was imported but no annotations were found " +
26                     "having both 'mode' and 'proxyTargetClass' attributes of type " +
27                     "AdviceMode and boolean respectively. This means that auto proxy " +
28                     "creator registration and configuration may not have occurred as " +
29                     "intended, and components may not be proxied as expected. Check to " +
30                     "ensure that %s has been @Import'ed on the same class where these " +
31                     "annotations are declared; otherwise remove the import of %s " +
32                     "altogether.", name, name, name));
33         }
34     }

Agent mode: aopconfigutils registerAutoProxyCreatorIfNecessary(registry);

The final call is: registerorexcalateapcasrequired (infrastructure advisor autoproxycreator. Class, registry, source); Infrastructure enhanced automatic proxy constructor

 1 private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
 2         Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
       //If the current registrar containsinternalAutoProxyCreator
3 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {//org.springframework.aop.config.internalAutoProxyCreator Internal automatic proxy constructor 4 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); 5 if (!cls.getName().equals(apcDefinition.getBeanClassName())) {//If the current class is notinternalAutoProxyCreator 6 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); 7 int requiredPriority = findPriorityForClass(cls); 8 if (currentPriority < requiredPriority) {//If the subscript is greater than the existing internal automatic proxy constructor, index The smaller the, the higher the priority,InfrastructureAdvisorAutoProxyCreator index=0,requiredPriority Minimum, no entry 9 apcDefinition.setBeanClassName(cls.getName()); 10 } 11 } 12 return null;//Direct return 13 }//If the current registrar does not containinternalAutoProxyCreator,Then take the current class as the root definition 14 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); 15 beanDefinition.setSource(source); 16 beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);//Highest priority 17 beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 18 registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); 19 return beanDefinition; 20 }

As shown in the figure above, APC_ PRIORITY_ The list is as follows:

 1 /**
 2      * Stores the auto proxy creator classes in escalation order.
 3      */
 4     private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<Class<?>>();
 5 
 6     /**
 7      * Priority up list
 8      */
 9     static {
10         APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
11         APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
12         APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
13     }

As shown in the figure above, because the first index of the class infrastructureasuggestorautoproxycreator in the list is 0, the requiredpriority is the smallest, and it does not enter, so the beanClassName is not reset, nothing is done, and null is returned

When will the enhanced proxy class be generated?

The class diagram of infrastructure advisor autoproxycreator is as follows:

As shown in the figure above, look at the two core methods: postprocessbeforeinstance of the InstantiationAwareBeanPostProcessor interface + postProcessAfterInitialization of the BeanPostProcessor interface. About spring bean life cycle airline ticket: Spring IOC (IV) summary and sublimation

 1     @Override
 2     public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
 3         Object cacheKey = getCacheKey(beanClass, beanName);
 4 
 5         if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
 6             if (this.advisedBeans.containsKey(cacheKey)) {//If it already exists, return directly
 7                 return null;
 8             }//Whether basic components (basic construction does not require agents): Advice, Pointcut, Advisor and AopInfrastructureBean are all basic construction
 9             if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
10                 this.advisedBeans.put(cacheKey, Boolean.FALSE);//Add intoadvisedBeans ConcurrentHashMap<k=Object,v=Boolean>Mark whether the implementation needs to be enhanced. Here is the basic construction bean No agent is required, all are set to false,For the backpostProcessAfterInitializationUse after instantiation.
11                 return null;
12             }
13         }
14 
15         // TargetSource yes spring aop The user-defined instantiated interface is reserved for us. If it existsTargetSource It will not be instantiated by default, but in a user-defined way. We have no definition and do not enter
18         if (beanName != null) {
19             TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
20             if (targetSource != null) {
21                 this.targetSourcedBeans.add(beanName);
22                 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
23                 Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
24                 this.proxyTypes.put(cacheKey, proxy.getClass());
25                 return proxy;
26             }
27         }
28 
29         return null;
30     }

Through tracing, since infrastructure advisor autoproxycreator is an infrastructure class,

advisedBeans.put(cacheKey, Boolean.FALSE)

Add the adviedbeans concurrenthashmap < k = object, v = Boolean > flag to indicate whether the implementation needs to be enhanced. Here, the basic build beans do not need an agent and are set to false for later use after the instantiation of postProcessAfterInitialization.

Let's look at the source code of postProcessAfterInitialization as follows:

 1     @Override
 2     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 3         if (bean != null) {
 4             Object cacheKey = getCacheKey(bean.getClass(), beanName);
 5             if (!this.earlyProxyReferences.contains(cacheKey)) {
 6                 return wrapIfNecessary(bean, beanName, cacheKey);
 7             }
 8         }
 9         return bean;
10     }
11 
12     protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
       // If it is a user-defined instance, you do not need to enhance the processing and return directly
13 if (beanName != null && this.targetSourcedBeans.contains(beanName)) { 14 return bean; 15 }// Query map cache,markrecord a demerit false,Direct return without enhancement 16 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { 17 return bean; 18 }// Judge spring AOPBasic construction classrecord a demerit false,Direct return without enhancement 19 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { 20 this.advisedBeans.put(cacheKey, Boolean.FALSE); 21 return bean; 22 } 23 24 // Get enhancements List<Advisor> advisors 25 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
       // If there are enhancements
26 if (specificInterceptors != DO_NOT_PROXY) { 27 this.advisedBeans.put(cacheKey, Boolean.TRUE);// Mark enhanced as TRUE,Indicates that the implementation needs to be enhanced
         // Generate enhanced proxy class
28 Object proxy = createProxy( 29 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); 30 this.proxyTypes.put(cacheKey, proxy.getClass()); 31 return proxy; 32 } 33      // If there is no enhancement, mark false as the cache and enter again to improve efficiency. In line 16, use the cache to verify first 34 this.advisedBeans.put(cacheKey, Boolean.FALSE); 35 return bean; 36 }

The core method createProxy is as follows:

 1     protected Object createProxy(
 2             Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
 3      // If it isConfigurableListableBeanFactoryInterface (US) DefaultListableBeanFactory Is the implementation class of the interface), then expose the target class
 4         if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
         //Define an attribute for beanfactory - > beandefinition: k=AutoProxyUtilsoriginalTargetClass,v=Need to be represented bean class
5 AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); 6 } 7 8 ProxyFactory proxyFactory = new ProxyFactory(); 9 proxyFactory.copyFrom(this); 10      //If not the proxy target class 11 if (!proxyFactory.isProxyTargetClass()) {//IfbeanFactory Defines the proxy target class( CGLIB) 12 if (shouldProxyTargetClass(beanClass, beanName)) { 13 proxyFactory.setProxyTargetClass(true);//Agent factory setting agent target class 14 } 15 else {//Otherwise, set the proxy interface (JDK) 16 evaluateProxyInterfaces(beanClass, proxyFactory); 17 } 18 } 19      //Package the interceptor as an enhancement (notification) 20 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); 21 proxyFactory.addAdvisors(advisors);//Set into the agent factory 22 proxyFactory.setTargetSource(targetSource); 23 customizeProxyFactory(proxyFactory);//The empty method is reserved for subclass expansion. It is a typical spring style and likes to leave a way behind everywhere 24      //It is used to control whether the agent factory can also add notifications again. The default is false((indicates not allowed) 25 proxyFactory.setFrozen(this.freezeProxy); 26 if (advisorsPreFiltered()) {//default false,The matching enhancement has been pre filtered above Advisor 27 proxyFactory.setPreFiltered(true); 28 } 29 //The core method for agent factory to obtain agent object 30 return proxyFactory.getProxy(getProxyClassLoader()); 31 }

Finally, we generate the CGLIB proxy class So far, we have analyzed the construction process of proxy class.

ProxyTransactionManagementConfiguration

Let's look at ProxyTransactionManagementConfiguration:

 1 @Configuration
 2 public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
 3 
 4     @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
 5     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)//Define transaction enhancer
 6     public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
 7         BeanFactoryTransactionAttributeSourceAdvisor j = new BeanFactoryTransactionAttributeSourceAdvisor();
 8         advisor.setTransactionAttributeSource(transactionAttributeSource());
 9         advisor.setAdvice(transactionInterceptor());
10         advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
11         return advisor;
12     }
13 
14     @Bean
15     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)//Define annotation based transaction attribute resources
16     public TransactionAttributeSource transactionAttributeSource() {
17         return new AnnotationTransactionAttributeSource();
18     }
19 
20     @Bean
21     @Role(BeanDefinition.ROLE_INFRASTRUCTURE)//Define transaction interceptors
22     public TransactionInterceptor transactionInterceptor() {
23         TransactionInterceptor interceptor = new TransactionInterceptor();
24         interceptor.setTransactionAttributeSource(transactionAttributeSource());
25         if (this.txManager != null) {
26             interceptor.setTransactionManager(this.txManager);
27         }
28         return interceptor;
29     }
30 
31 }

Core method: transactionAdvisor() transaction weaving

An advisor is defined to set transaction properties, transaction interceptor and order. The core is the transaction interceptor.

TransactionInterceptor uses the common spring transaction infrastructure to implement "declarative transactions", inherits from the TransactionAspectSupport class (which contains integration with spring's underlying transaction API) and implements the MethodInterceptor interface. The spring class diagram is as follows:

The interception function of the transaction interceptor depends on the implementation of the MethodInterceptor interface. Students familiar with spring must be familiar with the MethodInterceptor. This is the spring method interceptor, mainly based on the invoke method:

 1 @Override
 2     public Object invoke(final MethodInvocation invocation) throws Throwable {
 3         // Work out the target class: may be {@code null}.
 4         // The TransactionAttributeSource should be passed the target class
 5         // as well as the method, which may be from an interface.
 6         Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
 7 
 8         // call TransactionAspectSupport of invokeWithinTransaction method
 9         return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
10             @Override
11             public Object proceedWithInvocation() throws Throwable {
12                 return invocation.proceed();
13             }
14         });
15     }

As shown in figure TransactionInterceptor, the invoke method of the MethodInterceptor interface is duplicated and the invokeWithinTransaction() method of the parent class TransactionAspectSupport is invoked in the invoke method. The source code is as follows:

 1 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
 2             throws Throwable {  
 3 
 4         // If transaction attribute Empty,This method is a non transaction (non programmatic transaction)
 5         final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
 6         final PlatformTransactionManager tm = determineTransactionManager(txAttr);
 7         final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
 8      // Standard declarative transaction: if the transaction attribute is empty or non callback biasedTransaction manager
 9         if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
10             // Standard transaction demarcation with getTransaction and commit/rollback calls.
11             TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
12             Object retVal = null;
13             try {
14                 // Here is a surround enhancement, in this proceed You can define the enhanced implementation before and after
15                 // Method execution
16                 retVal = invocation.proceedWithInvocation();
17             }
18             catch (Throwable ex) {
19                 // According to the transaction definition, the exception needs to be rolled back, otherwise the transaction is committed
20                 completeTransactionAfterThrowing(txInfo, ex);
21                 throw ex;
22             }
23             finally {//Clear the current transaction information and reset it to the old one
24                 cleanupTransactionInfo(txInfo);
25             }//Commit transactions before returning results
26             commitTransactionAfterReturning(txInfo);
27             return retVal;
28         }
29      // Programming transactions: (callback bias)
30         else {
31             final ThrowableHolder throwableHolder = new ThrowableHolder();
32 
33             // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
34             try {
35                 Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
36                         new TransactionCallback<Object>() {
37                             @Override
38                             public Object doInTransaction(TransactionStatus status) {
39                                 TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
40                                 try {
41                                     return invocation.proceedWithInvocation();
42                                 }
43                                 catch (Throwable ex) {// IfException needs to be rolled back
44                                     if (txAttr.rollbackOn(ex)) {
45                                         // If it is a runtime exception, return
46                                         if (ex instanceof RuntimeException) {
47                                             throw (RuntimeException) ex;
48                                         }// If it is any other exception, throw itThrowableHolderException
49                                         else {
50                                             throw new ThrowableHolderException(ex);
51                                         }
52                                     }// If rollback is not required
53                                     else {
54                                         // Define the exception and eventually commit the transaction directly
55                                         throwableHolder.throwable = ex;
56                                         return null;
57                                     }
58                                 }
59                                 finally {//Clear the current transaction information and reset it to the old one
60                                     cleanupTransactionInfo(txInfo);
61                                 }
62                             }
63                         });
64 
65                 // Throw up anomaly
66                 if (throwableHolder.throwable != null) {
67                     throw throwableHolder.throwable;
68                 }
69                 return result;
70             }
71             catch (ThrowableHolderException ex) {
72                 throw ex.getCause();
73             }
74             catch (TransactionSystemException ex2) {
75                 if (throwableHolder.throwable != null) {
76                     logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
77                     ex2.initApplicationException(throwableHolder.throwable);
78                 }
79                 throw ex2;
80             }
81             catch (Throwable ex2) {
82                 if (throwableHolder.throwable != null) {
83                     logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
84                 }
85                 throw ex2;
86             }
87         }
88     }

As shown in the figure above, we mainly look at the first branch, declarative transaction. The core process is as follows:

1.createTransactionIfNecessary(): create a transaction if necessary

2.InvocationCallback's proceedWithInvocation(): InvocationCallback is the internal callback interface of the parent class, which is implemented in the subclass for the parent class to call, and invocationinterceptor in the subclass proceed(). Callback method execution

3. Abnormal rollback completeTransactionAfterThrowing()

1.createTransactionIfNecessary():

 1 protected TransactionInfo createTransactionIfNecessary(
 2             PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
 3 
 4         // If you haven't defined a name, put the name of the connection point ID Defines the name of the transaction
 5         if (txAttr != null && txAttr.getName() == null) {
 6             txAttr = new DelegatingTransactionAttribute(txAttr) {
 7                 @Override
 8                 public String getName() {
 9                     return joinpointIdentification;
10                 }
11             };
12         }
13 
14         TransactionStatus status = null;
15         if (txAttr != null) {
16             if (tm != null) {
17                 status = tm.getTransaction(txAttr);
18             }
19             else {
20                 if (logger.isDebugEnabled()) {
21                     logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
22                             "] because no transaction manager has been configured");
23                 }
24             }
25         }
26         return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
27     }

The core is:

1) getTransaction(), get the TransactionStatus of the transaction according to the transaction attribute. All of them call platformtransactionmanager getTransaction(), see 3.3.1 for the source code.
2) prepareTransactionInfo(), construct a TransactionInfo transaction information object and bind the current thread: ThreadLocal < TransactionInfo >.

2.invocation.proceed() callback business method:

The final implementation class is ReflectiveMethodInvocation. The class diagram is as follows:

As shown in the figure above, the reflective methodinvocation class implements the proxymethodinvocation interface, but proxymethodinvocation inherits the three-tier interface ProxyMethodInvocation->MethodInvocation->Invocation->Joinpoint

Joinpoint: connection point interface, which defines the execution interface: object processed() throws throwable; Executes the current connection point and jumps to the next interceptor on the interceptor chain.

Invocation: the calling interface, inherited from Joinpoint, defines the interface for obtaining parameters: Object[] getArguments(); It is a connection point with parameters that can be intercepted by interceptors.

MethodInvocation: method calling interface, which inherits from Invocation and defines the interface of obtaining method: Method getMethod(); Is an interceptable connection point method with parameters.

ProxyMethodInvocation: proxy method calling interface, which inherits from MethodInvocation and defines the interface for obtaining proxy object: Object getProxy(); Is a method call join point method executed by a proxy class.

Reflective methodinvocation: implements the ProxyMethodInvocation interface, which naturally implements all interfaces of the parent class interface. Get the proxy class, get the method, get the parameters, use the proxy class to execute this method and automatically jump to the next connection point.

Let's take a look at the source code of the proceed method:

 1 @Override
 2     public Object proceed() throws Throwable {
 3         //    Index at startup is-1,Wake up the connection point and increase it later
 4         if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
 5             return invokeJoinpoint();
 6         }
 7 
 8         Object interceptorOrInterceptionAdvice =
 9                 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
10         if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
11             // Dynamic method matching verification is performed here. Static method matching has already been verified(MethodMatcherThere are two typical interfaces: dynamic/Static verification)
13             InterceptorAndDynamicMethodMatcher dm =
14                     (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
15             if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
16                 return dm.interceptor.invoke(this);
17             }
18             else {
19                 // Dynamic matching failed. Skip the current interception and enter the next one (interceptor chain)
21                 return proceed();
22             }
23         }
24         else {
25             // It is an interceptor, so we only call it:Before constructing this object, the pointcut will be statically evaluated.
27             return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
28         }
29     }

What we finally call here is ((methodinterceptor) interceptorinterceptionadvice) invoke(this); The transaction interceptor is the transaction interceptor callback target business method (addUserBalanceAndUser).

3.completeTransactionAfterThrowing()

Finally, call the rollback() of AbstractPlatformTransactionManager to commit the transaction commitTransactionAfterReturning() and finally call the commit() of AbstractPlatformTransactionManager. See 3.3.3 for the source code

Summary:

It can be seen that whether it is a programmatic transaction or a declarative transaction, the final source code is to call the three methods of the PlatformTransactionManager interface of the transaction manager:

  1. getTransaction
  2. commit
  3. rollback

In the next section, let's take a look at how the transaction management implements these three methods.

 

3, Transaction core source code

Let's take a look at the core class diagram:

As mentioned above, the PlatformTransactionManager top-level interface defines the most core transaction management methods. The following layer is the AbstractPlatformTransactionManager abstract class, which implements the methods of the PlatformTransactionManager interface and defines some abstract methods for subclass expansion. Finally, the following layer is two classic transaction managers:

1.DataSourceTransactionmanager, namely JDBC single database transaction manager, is implemented based on Connection,

2. JTA transaction manager, that is, multi database transaction manager (also known as distributed transaction manager), implements JTA specification and uses XA protocol for two-stage submission.

We only look at the source code of DataSourceTransactionmanager based on JDBC connection.

PlatformTransactionManager interface:

1 public interface PlatformTransactionManager {
2     // Get transaction status
3     TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
4   // Transaction commit
5     void commit(TransactionStatus status) throws TransactionException;
6   // Transaction rollback
7     void rollback(TransactionStatus status) throws TransactionException;
8 }

1. getTransaction get transaction

AbstractPlatformTransactionManager implements getTransaction() method as follows:

 1     @Override
 2     public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
 3         Object transaction = doGetTransaction();
 4 
 5         // Cache debug flag to avoid repeated checks.
 6         boolean debugEnabled = logger.isDebugEnabled();
 7 
 8         if (definition == null) {
 9             // Use defaults if no transaction definition given.
10             definition = new DefaultTransactionDefinition();
11         }
12       // If a transaction already exists
13         if (isExistingTransaction(transaction)) {
14             // Simultaneous interpreting according to different communication mechanisms
15             return handleExistingTransaction(definition, transaction, debugEnabled);
16         }
17 
18         // Timeout cannot be less than the default value
19         if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
20             throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
21         }
22 
23         // There is no transaction propagation mechanism at present=MANDATORY(Support the current transaction, and report an error if there is no transaction
24         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
25             throw new IllegalTransactionStateException(
26                     "No existing transaction found for transaction marked with propagation 'mandatory'");
27         }// No transaction currently exists,Communication mechanism=REQUIRED/REQUIRED_NEW/NESTED,In these three cases, you need to start a new transaction and add transaction synchronization
28         else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
29                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
30                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
31             SuspendedResourcesHolder suspendedResources = suspend(null);
32             if (debugEnabled) {
33                 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
34             }
35             try {// Do you need to start synchronization again// open// open
36                 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
37                 DefaultTransactionStatus status = newTransactionStatus(
38                         definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
39                 doBegin(transaction, definition);// Open new transaction
40                 prepareSynchronization(status, definition);//Preparatory synchronization
41                 return status;
42             }
43             catch (RuntimeException ex) {
44                 resume(null, suspendedResources);
45                 throw ex;
46             }
47             catch (Error err) {
48                 resume(null, suspendedResources);
49                 throw err;
50             }
51         }
52         else {
53             // No transaction currently existsCurrently, there is no transaction and the propagation mechanism=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,In these three cases, create an "empty" transaction:There is no actual transaction, but it may be synchronous. Warning: isolation level is defined, but there is no real transaction initialization. Isolation level is ignoredThere is an isolation level, but the actual transaction initialization is not defined,There is an isolation level, but the actual transaction initialization is not defined,
54             if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
55                 logger.warn("Custom isolation level specified but no actual transaction initiated; " +
56                         "isolation level will effectively be ignored: " + definition);
57             }
58             boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
59             return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
60         }
61     }

As shown in the figure above, the source code is 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. no transactions exist at present: simultaneous interpreting of different communication mechanisms.

The source code of handleExistingTransaction() is as follows:

 1 private TransactionStatus handleExistingTransaction(
 2             TransactionDefinition definition, Object transaction, boolean debugEnabled)
 3             throws TransactionException {
 4      // 1.NERVER(The current transaction is not supported;If the current transaction exists, throw an exception and report an error
 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)
10         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
11             if (debugEnabled) {
12                 logger.debug("Suspending current transaction");
13             }
14             Object suspendedResources = suspend(transaction);
15             boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
16             return prepareTransactionStatus(
17                     definition, null, false, newSynchronization, debugEnabled, suspendedResources);
18         }
19       // 3.REQUIRES_NEW Suspend the current transaction and create a new transaction
20         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
21             if (debugEnabled) {
22                 logger.debug("Suspending current transaction, creating new transaction with name [" +
23                         definition.getName() + "]");
24             }// Suspend current transaction
25             SuspendedResourcesHolder suspendedResources = suspend(transaction);
26             try {// Create a new transaction
27                 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
28                 DefaultTransactionStatus status = newTransactionStatus(
29                         definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
30                 doBegin(transaction, definition);
31                 prepareSynchronization(status, definition);
32                 return status;
33             }
34             catch (RuntimeException beginEx) {
35                 resumeAfterBeginException(transaction, suspendedResources, beginEx);
36                 throw beginEx;
37             }
38             catch (Error beginErr) {
39                 resumeAfterBeginException(transaction, suspendedResources, beginErr);
40                 throw beginErr;
41             }
42         }
43      // 4.NESTED Nested transaction
44         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
45             if (!isNestedTransactionAllowed()) {
46                 throw new NestedTransactionNotSupportedException(
47                         "Transaction manager does not allow nested transactions by default - " +
48                         "specify 'nestedTransactionAllowed' property with value 'true'");
49             }
50             if (debugEnabled) {
51                 logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
52             }// Support savepoint:wrong JTA Transactions take this branch. AbstractPlatformTransactionManager Default is true,JtaTransactionManager The method is duplicated false,DataSourceTransactionmanager No copy, or true,
53             if (useSavepointForNestedTransaction()) { 
54                 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
55                 DefaultTransactionStatus status =
56                         prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
57                 status.createAndHoldSavepoint();// Create savepoint
58                 return status;
59             }
60             else {
61                 // JTA Transactions take this branch,Create a new transaction
62                 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
63                 DefaultTransactionStatus status = newTransactionStatus(
64                         definition, transaction, true, newSynchronization, debugEnabled, null);
65                 doBegin(transaction, definition);
66                 prepareSynchronization(status, definition);
67                 return status;
68             }
69         }
70 
71         
72         if (debugEnabled) {
73             logger.debug("Participating in existing transaction");
74         }
75         if (isValidateExistingTransaction()) {
76             if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
77                 Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
78                 if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
79                     Constants isoConstants = DefaultTransactionDefinition.constants;
80                     throw new IllegalTransactionStateException("Participating transaction with definition [" +
81                             definition + "] specifies isolation level which is incompatible with existing transaction: " +
82                             (currentIsolationLevel != null ?
83                                     isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
84                                     "(unknown)"));
85                 }
86             }
87             if (!definition.isReadOnly()) {
88                 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
89                     throw new IllegalTransactionStateException("Participating transaction with definition [" +
90                             definition + "] is not marked as read-only but existing transaction is");
91                 }
92             }
93         }// Come here PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED orPROPAGATION_MANDATORY,If a transaction exists, you can join the transaction, prepareTransactionStatus The third parameter is whether a new transaction is required. false Means you don't need anything new
94         boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
95         return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
96     }

As shown in the figure above, when the current thread already has a transaction, the new processing of different isolation levels:

1.NERVER: current transaction is not supported; If the current transaction exists, an exception is thrown: "Existing transaction found for transaction marked with propagation 'never'"
2.NOT_SUPPORTED: current transaction is not supported, existing synchronization will be suspended: suspend()
3.REQUIRES_NEW suspends the current transaction and creates a new transaction:

  1)suspend()

  2)doBegin()
4.NESTED transactions

1) non JTA transaction: createAndHoldSavepoint() creates JDBC 3 0 savepoint, no synchronization required

2) JTA transaction: start a new transaction. Dobegin() + preparessynchronization() needs to be synchronized

There are several core methods: suspend the current transaction () and start a new transaction doBegin().

The source code of suspend() is as follows:

 1 protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
 2         if (TransactionSynchronizationManager.isSynchronizationActive()) {// 1.Synchronization currently exists,
 3             List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
 4             try {
 5                 Object suspendedResources = null;
 6                 if (transaction != null) {// Transaction is not empty, suspend transaction
 7                     suspendedResources = doSuspend(transaction);
 8                 }// Unbind various attributes of the current transaction: name, read-only, isolation level, and whether it is a real transaction.
 9                 String name = TransactionSynchronizationManager.getCurrentTransactionName();
10                 TransactionSynchronizationManager.setCurrentTransactionName(null);
11                 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
12                 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
13                 Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
14                 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
15                 boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
16                 TransactionSynchronizationManager.setActualTransactionActive(false);
17                 return new SuspendedResourcesHolder(
18                         suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
19             }
20             catch (RuntimeException ex) {
21                 // doSuspend failed - original transaction is still active...
22                 doResumeSynchronization(suspendedSynchronizations);
23                 throw ex;
24             }
25             catch (Error err) {
26                 // doSuspend failed - original transaction is still active...
27                 doResumeSynchronization(suspendedSynchronizations);
28                 throw err;
29             }
30         }// 2.There is no synchronization, but the transaction is not empty. The transaction is suspended
31         else if (transaction != null) {
32             // Transaction active but no synchronization active.
33             Object suspendedResources = doSuspend(transaction);
34             return new SuspendedResourcesHolder(suspendedResources);
35         }// 2.There is no synchronization, but the transaction is empty and nothing needs to be done
36         else {
37             // Neither transaction nor synchronization active.
38             return null;
39         }
40     }

doSuspend(), suspends the transaction. The AbstractPlatformTransactionManager abstract class doSuspend() will report an error: suspension is not supported. If the specific transaction executor supports it, copy doSuspend(), and the implementation of DataSourceTransactionManager is as follows:

1 @Override
2     protected Object doSuspend(Object transaction) {
3         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
4         txObject.setConnectionHolder(null);
5         return TransactionSynchronizationManager.unbindResource(this.dataSource);
6     }

The core operations of suspending DataSourceTransactionManager transactions are:

1. Clear the connectionHolder database connection holder of the current transaction.

2. Unbind datasource from current thread In fact, ThreadLocal removes the corresponding variable (private static final ThreadLocal < map < object, Object > > resources = new namedthreadlocal < map < object, Object > > ("transactional resources")

TransactionSynchronizationManager transaction synchronization manager. This class maintains multiple thread local variables ThreadLocal, as shown in the following figure:

 1 public abstract class TransactionSynchronizationManager {
 2 
 3     private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
 4     // Transaction resources: Map < K, V > two data pairs. 1. Session factory and sessionk=SqlsessionFactory v=SqlSessionHolder 2.Data sources and connectionsk=DataSource v=ConnectionHolder
 5     private static final ThreadLocal<Map<Object, Object>> resources =
 6             new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
 7     // Transaction synchronization
 8     private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
 9             new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
10   // Current transaction name
11     private static final ThreadLocal<String> currentTransactionName =
12             new NamedThreadLocal<String>("Current transaction name");
13   // Read only attribute of the current transaction
14     private static final ThreadLocal<Boolean> currentTransactionReadOnly =
15             new NamedThreadLocal<Boolean>("Current transaction read-only status");
16   // Isolation level of the current transaction
17     private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
18             new NamedThreadLocal<Integer>("Current transaction isolation level");
19   // Is there a transaction
20     private static final ThreadLocal<Boolean> actualTransactionActive =
21             new NamedThreadLocal<Boolean>("Actual transaction active");
22 . . . 
23 }

The source code of doBegin() is as follows:

 1 @Override
 2     protected void doBegin(Object transaction, TransactionDefinition definition) {
 3         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 4         Connection con = null;
 5 
 6         try {// If the transaction has not been connection perhaps connection In the transaction synchronization state, reset the new connectionHolder
 7             if (!txObject.hasConnectionHolder() ||
 8                     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
 9                 Connection newCon = this.dataSource.getConnection();
10                 if (logger.isDebugEnabled()) {
11                     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
12                 }// Reset NEW connectionHolder
13                 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
14             }
15        //Set the new connection as in transaction synchronization
16             txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
17             con = txObject.getConnectionHolder().getConnection();
18          //conn set transaction isolation level, read-only
19             Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
20             txObject.setPreviousIsolationLevel(previousIsolationLevel);//Set transaction isolation level for DataSourceTransactionObject
21 
22             // If it is automatic submission, switch to manual submission
23             // so we don't want to do it unnecessarily (for example if we've explicitly
24             // configured the connection pool to set it already).
25             if (con.getAutoCommit()) {
26                 txObject.setMustRestoreAutoCommit(true);
27                 if (logger.isDebugEnabled()) {
28                     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
29                 }
30                 con.setAutoCommit(false);
31             }
32        // If read-only, execute sql Set transaction read-only
33             prepareTransactionalConnection(con, definition);
34             txObject.getConnectionHolder().setTransactionActive(true);// set upconnection holderTransaction on state of
35 
36             int timeout = determineTimeout(definition);
37             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
38                 txObject.getConnectionHolder().setTimeoutInSeconds(timeout);// set upTimeout seconds
39             }
40 
41             // binding connection To the current thread
42             if (txObject.isNewConnectionHolder()) {
43                 TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
44             }
45         }
46 
47         catch (Throwable ex) {
48             if (txObject.isNewConnectionHolder()) {
49                 DataSourceUtils.releaseConnection(con, this.dataSource);
50                 txObject.setConnectionHolder(null, false);
51             }
52             throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
53         }
54     }

As shown in the figure above, the core operations of doBegin() are:

1.DataSourceTransactionObject "data source transaction object", set ConnectionHolder, and then set various properties for ConnectionHolder: automatic submission, timeout, transaction opening and isolation level.

2. Bind a thread local variable to the current thread, key=DataSource, data source = v=ConnectionHolder database connection.

 

2. commit transaction

1, Before explaining the source code, take a look at the resource management class:

SqlSessionSynchronization is an internal class of SqlSessionUtils, which inherits from the TransactionSynchronizationAdapter abstract class and implements the transaction synchronization interface TransactionSynchronization.

The class diagram is as follows:

The TransactionSynchronization interface defines the corresponding resource (JDBC transaction, then SqlSessionSynchronization) management method during transaction operation:

 1     // Pending transactions   
2   void suspend(); 3 // Wake up transaction    4   void resume(); 5 6 void flush(); 7 8 // Before committing a transaction 9 void beforeCommit(boolean readOnly); 10 11 // Before the commit transaction is completed 12 void beforeCompletion(); 13 14 // After committing a transaction 15 void afterCommit(); 16 17 // After the commit transaction is completed 18 void afterCompletion(int status);

Many of them use these interfaces to manage transactions.

2, commit transaction

 

The commit source code of AbstractPlatformTransactionManager is as follows:

 1 @Override
 2     public final void commit(TransactionStatus status) throws TransactionException {
 3         if (status.isCompleted()) {// If the transaction is completed, an error is reported and cannot be committed again
 4             throw new IllegalTransactionStateException(
 5                     "Transaction is already completed - do not call commit or rollback more than once per transaction");
 6         }
 7 
 8         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
 9         if (defStatus.isLocalRollbackOnly()) {// If the transaction is explicitly marked for rollback,
10             if (defStatus.isDebug()) {
11                 logger.debug("Transactional code has requested rollback");
12             }
13             processRollback(defStatus);//Execute rollback
14             return;
15         }//If global rollback is not required, commit and global rollback
16         if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
17             if (defStatus.isDebug()) {
18                 logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
19             }//Execute rollback
20             processRollback(defStatus);
21             // Throw an "unexpected rollback exception" only at the outermost transaction boundary (new transaction) or when explicitly requested
23             if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
24                 throw new UnexpectedRollbackException(
25                         "Transaction rolled back because it has been marked as rollback-only");
26             }
27             return;
28         }
29      // Execute commit transaction
30         processCommit(defStatus);
31     }

As shown in the figure above, various judgments:

  • 1. If the transaction is explicitly marked as local rollback, rollback is executed
  • 2. If global rollback is not required, commit and execute rollback
  • 3. Commit the transaction. The core method is processCommit()

processCommit is as follows:

 1 private void processCommit(DefaultTransactionStatus status) throws TransactionException {
 2         try {
 3             boolean beforeCompletionInvoked = false;
 4             try {//3 Previous operations
 5                 prepareForCommit(status);
 6                 triggerBeforeCommit(status);
 7                 triggerBeforeCompletion(status);
 8                 beforeCompletionInvoked = true;//3Pre operation called
 9                 boolean globalRollbackOnly = false;//New transaction or global rollback failed
10                 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
11                     globalRollbackOnly = status.isGlobalRollbackOnly();
12                 }//1. There are savepoints, that is, nested transactions
13                 if (status.hasSavepoint()) {
14                     if (status.isDebug()) {
15                         logger.debug("Releasing transaction savepoint");
16                     }//Release savepoint
17                     status.releaseHeldSavepoint();
18                 }//2. New services
19                 else if (status.isNewTransaction()) {
20                     if (status.isDebug()) {
21                         logger.debug("Initiating transaction commit");
22                     }//Call the transaction processor to commit the transaction
23                     doCommit(status);
24                 }
25                 // 3.If the transaction is not a new transaction and the global rollback fails, but no exception is received when committing, an exception is thrown
27                 if (globalRollbackOnly) {
28                     throw new UnexpectedRollbackException(
29                             "Transaction silently rolled back because it has been marked as rollback-only");
30                 }
31             }
32             catch (UnexpectedRollbackException ex) {
33                 // After triggering, the transaction is synchronized and the status is rollback
34                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
35                 throw ex;
36             }// Transaction exception
37             catch (TransactionException ex) {
38                 // Commit failed rollback
39                 if (isRollbackOnCommitFailure()) {
40                     doRollbackOnCommitException(status, ex);
41                 }// After triggering the callback, the transaction synchronization status is unknown
42                 else {
43                     triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
44                 }
45                 throw ex;
46             }// Runtime exception
47             catch (RuntimeException ex) {
            // If the three preceding steps are not completed, call the last preceding step
48 if (!beforeCompletionInvoked) { 49 triggerBeforeCompletion(status); 50 }// Commit exception rollback 51 doRollbackOnCommitException(status, ex); 52 throw ex; 53 }// Other anomalies 54 catch (Error err) {  
            // If the three preceding steps are not completed, call the last preceding step
55 if (!beforeCompletionInvoked) { 56 triggerBeforeCompletion(status); 57 }// Commit exception rollback 58 doRollbackOnCommitException(status, err); 59 throw err; 60 } 61 62 // Trigger afterCommit callbacks, with an exception thrown there 63 // propagated to callers but the transaction still considered as committed. 64 try { 65 triggerAfterCommit(status); 66 } 67 finally { 68 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); 69 } 70 71 } 72 finally { 73 cleanupAfterCompletion(status); 74 } 75 }

As shown in the figure above, there are 6 core operations during commit transaction, including 3 pre operations and 3 post operations, as follows:

1.prepareForCommit(status); The source code is empty and has not been expanded at present.

2.triggerBeforeCommit(status); Trigger operation before submitting

1 protected final void triggerBeforeCommit(DefaultTransactionStatus status) {
2         if (status.isNewSynchronization()) {
3             if (status.isDebug()) {
4                 logger.trace("Triggering beforeCommit synchronization");
5             }
6             TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());
7         }
8     }

The source code of triggerBeforeCommit is as follows:

1 public static void triggerBeforeCommit(boolean readOnly) {
2         for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
3             synchronization.beforeCommit(readOnly);
4         }
5     }

As shown in the figure above, the TransactionSynchronizationManager class defines multiple ThreadLocal (thread local variables), one of which is used to save the transaction synchronization of the current thread:

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

Traverse the transaction synchronizer and execute the "before commit" operation for each transaction synchronizer. For example, the jdbc transaction we use will eventually be sqlsessionutils beforeCommit()->this. holder. getSqlSession(). commit(); Submit session. (because the source code is a spring management practice, transaction submission will not be executed in the end, for example, DefaultSqlSession: clear the cache and reset the state)

3.triggerBeforeCompletion(status); Trigger the operation before completion. If it is a jdbc transaction, it will eventually be,

SqlSessionUtils.beforeCompletion->

TransactionSynchronizationManager.unbindResource(sessionFactory); Unbind the session factory of the current thread

this.holder.getSqlSession().close(); Close the session. (because the source code is a spring management practice, the transaction close operation will not be performed in the end, such as DefaultSqlSession, and various clearing and closing operations will also be performed)

4.triggerAfterCommit(status); The action is triggered after the transaction is committed. TransactionSynchronizationUtils. triggerAfterCommit();-> TransactionSynchronizationUtils. Invokeaftercommit, as follows:

1 public static void invokeAfterCommit(List<TransactionSynchronization> synchronizations) {
2         if (synchronizations != null) {
3             for (TransactionSynchronization synchronization : synchronizations) {
4                 synchronization.afterCommit();
5             }
6         }
7     }

Well, after a meal, it was copied in the transaction synchronization adapter and empty SqlSessionSynchronization inherits the TransactionSynchronizationAdapter but does not replicate this method.

5. triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);

TransactionSynchronizationUtils.TransactionSynchronizationUtils.invokeAfterCompletion, as follows:

 1 public static void invokeAfterCompletion(List<TransactionSynchronization> synchronizations, int completionStatus) {
 2         if (synchronizations != null) {
 3             for (TransactionSynchronization synchronization : synchronizations) {
 4                 try {
 5                     synchronization.afterCompletion(completionStatus);
 6                 }
 7                 catch (Throwable tsex) {
 8                     logger.error("TransactionSynchronization.afterCompletion threw exception", tsex);
 9                 }
10             }
11         }
12     }

After completion: for JDBC transactions, the final:

1) If the session is still alive, close the session,

2) Reset various attributes: referenceCount reference count of SQL session holder of SQL session synchronizer, synchronizedWithTransaction synchronization transaction, rollback only, deadline timeout point.

6.cleanupAfterCompletion(status);

1) Set the transaction status to completed.

2) If it is a new transaction synchronization, unbind the database resources bound by the current thread and reset the database connection

3) If there is a pending transaction (nested transaction), wake up various resources of the pending old transaction: database resources and synchronizer.

 1     private void cleanupAfterCompletion(DefaultTransactionStatus status) {
 2         status.setCompleted();//Set transaction status complete
       //If it is a new synchronization, clear all thread local variables bound by the current thread except resources: including transaction synchronizer, transaction name, read-only attribute, isolation level and real transaction activation status
3 if (status.isNewSynchronization()) { 4 TransactionSynchronizationManager.clear(); 5 }//If it is a new transaction synchronization 6 if (status.isNewTransaction()) { 7 doCleanupAfterCompletion(status.getTransaction()); 8 }//If there are pending resources 9 if (status.getSuspendedResources() != null) { 10 if (status.isDebug()) { 11 logger.debug("Resuming suspended transaction after completion of inner transaction"); 12 }//Wake up the suspended transactions and resources (rebind the previously suspended database resources, wake up the synchronizer, and register the synchronizer with the transaction synchronization manager) 13 resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources()); 14 } 15 }

For DataSourceTransactionManager, the source code of doCleanupAfterCompletion is as follows:

 1     protected void doCleanupAfterCompletion(Object transaction) {
 2         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 3 
 4         // If it is the latest connection holder, unbind the current thread<Database resources,ConnectionHolder>
 5         if (txObject.isNewConnectionHolder()) {
 6             TransactionSynchronizationManager.unbindResource(this.dataSource);
 7         }
 8 
 9         // Reset database connection (isolation level, read-only)
10         Connection con = txObject.getConnectionHolder().getConnection();
11         try {
12             if (txObject.isMustRestoreAutoCommit()) {
13                 con.setAutoCommit(true);
14             }
15             DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
16         }
17         catch (Throwable ex) {
18             logger.debug("Could not reset JDBC Connection after transaction", ex);
19         }
20 
21         if (txObject.isNewConnectionHolder()) {
22             if (logger.isDebugEnabled()) {
23                 logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
24             }// Resource reference count-1,Close database connection
25             DataSourceUtils.releaseConnection(con, this.dataSource);
26         }
27         // Reset all properties of the connection holder
28         txObject.getConnectionHolder().clear();
29     }

 

3. rollback rollback transaction

The source code of rollback in AbstractPlatformTransactionManager is as follows:

1     public final void rollback(TransactionStatus status) throws TransactionException {
2         if (status.isCompleted()) {
3             throw new IllegalTransactionStateException(
4                     "Transaction is already completed - do not call commit or rollback more than once per transaction");
5         }
6 
7         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
8         processRollback(defStatus);
9     }

The source code of processRollback is as follows:

 1     private void processRollback(DefaultTransactionStatus status) {
 2         try {
 3             try {// Unbind the session factory bound by the current thread and close the session
 4                 triggerBeforeCompletion(status);
 5                 if (status.hasSavepoint()) {// 1.If there is a savepoint, it is a nested transaction
 6                     if (status.isDebug()) {
 7                         logger.debug("Rolling back transaction to savepoint");
 8                     }//Rollback to savepoint
 9                     status.rollbackToHeldSavepoint();
10                 }//2. If it is a simple transaction
11                 else if (status.isNewTransaction()) {
12                     if (status.isDebug()) {
13                         logger.debug("Initiating transaction rollback");
14                     }//Rollback core method
15                     doRollback(status);
16                 }//3. There is a current transaction and there is no savepoint, that is, to join the current transaction
17                 else if (status.hasTransaction()) {//If it has been marked for rollback or when the join transaction failsGlobal rollback (default) true)
18                     if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
19                         if (status.isDebug()) {//debug Print: failed to join the transaction-Mark existing transaction as rollback
20                             logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
21                         }//Set the current connectionHolder: roll back when joining an existing transaction
22                         doSetRollbackOnly(status);
23                     }
24                     else {
25                         if (status.isDebug()) {
26                             logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
27                         }
28                     }
29                 }
30                 else {
31                     logger.debug("Should roll back transaction but cannot - no transaction available");
32                 }
33             }
34             catch (RuntimeException ex) {//Close the session and reset the SqlSessionHolder property
35                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
36                 throw ex;
37             }
38             catch (Error err) {
39                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
40                 throw err;
41             }
42             triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
43         }
44         finally {,,Unbind the current thread
45             cleanupAfterCompletion(status);
46         }
47     }

As shown in the figure above, there are several common methods consistent with the transaction submission, so they will not be repeated.

Here we mainly look at doRollback. The source code of doRollback() of DataSourceTransactionManager is as follows:

 1 protected void doRollback(DefaultTransactionStatus status) {
 2         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
 3         Connection con = txObject.getConnectionHolder().getConnection();
 4         if (status.isDebug()) {
 5             logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
 6         }
 7         try {
 8             con.rollback();
 9         }
10         catch (SQLException ex) {
11             throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
12         }
13     }

Well, it's not complicated at all. It's the rollback of Connection

IV. sequence diagram

Specially sorted out the sequence diagram (simple new transactions, no savepoints, etc.) as follows:

 

===========Reference========

Chapter 4 of Spring Combat 4: aspect oriented Spring

SpringBoot transaction annotation @ Transactional