Hello, everyone. Today, let's learn about the principle of @ Transaction transaction in spring through the source code.
Before starting this article, you need to understand the following knowledge in advance
1,Thoroughly understand Spring AOP
2,Spring programmatic transaction source code analysis
Two sentences are inserted here. The knowledge before and after the whole series is dependent. You'd better read it in order so that you won't be unable to understand. If you skip reading, you may be confused...
1. Environment
- jdk1.8
- Spring version: 5.2.3.RELEASE
- mysql5.7
2. Usage of @ Transaction transaction Transaction
Let's first review the usage of @ Transaction transaction Transaction, which is particularly simple and consists of two steps
1. Add the @ Transaction annotation on the method that requires spring to manage transactions
2. It is particularly important to add the @ EnableTransactionManagement annotation on the spring configuration class. Don't forget that the @ Trasaction annotation method will take effect only after this annotation.
3. @ Transaction transaction principle
The principle is relatively simple. Internally, the function of spring aop is used to intercept the execution of @ Transaction method through interceptors, and the function of adding transactions before and after the method.
4. @ EnableTransactionManagement annotation function
@The EnableTransactionManagement annotation will enable spring to automatically manage transactions. With this annotation, the creation process of all beans will be intercepted during the startup of the spring container to determine whether the bean needs spring to manage transactions, that is, whether there is @ Transaction annotation in the bean. The judgment rules are as follows
1. Keep looking up the class of the current bean, first from the current class, then the parent class, the parent class of the parent class, the interface of the current class, the parent interface of the interface, and the parent interface of the parent interface. Check whether there is @ Transaction annotation on these types
2. Is there a @ Transaction annotation on any public method of the class
If the bean meets any of the above rules, the spring container will create an agent through aop, and an interceptor will be added to the agent
org.springframework.transaction.interceptor.TransactionInterceptor
TransactionInterceptor interceptor is the key. It intercepts the execution of @ Trasaction method and adds transaction function before and after the method execution. Most of this interceptor is programmed transaction code. If Source code of programming transaction You see, the interceptor source code looks like pediatrics.
5. @ EnableTransactionManagement source code analysis
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) //@1
public @interface EnableTransactionManagement {
// Class based proxy (cglib) or interface based proxy (jdk dynamic proxy),defaultbyfalse,The representation is based onjdk Dynamic agent
boolean proxyTargetClass() default false;
//The notification mode is aop by default
AdviceMode mode() default AdviceMode.PROXY;
// We know that the function of this annotation is finally realized through aop. A proxy is created for the bean, and an interceptor is added to the proxy
// When there are other interceptors in the agent, the order of transaction interceptors can be specified through the order attribute
// The default value is LOWEST_PRECEDENCE = Integer.MAX_VALUE, the execution order of the interceptor is order ascending
int order() default Ordered.LOWEST_PRECEDENCE;
}
Notice @1 this code
@Import(TransactionManagementConfigurationSelector.class)
The @ Import annotation is used. Those unfamiliar with this annotation can have a look Spring series Chapter 18: @ import details (bean batch registration) , the value of this annotation is TransactionManagementConfigurationSelector. Take a look at the source code of this class, focusing on its selectImports method. This method will return an array of class names. This method will be called automatically during the startup of the spring container to register the class specified by this method in the spring container; The parameter of the method is AdviceMode, which is the value of the mode attribute in the @ EnableTransactionManagement annotation. The default is PROXY, so it will go to the @ 1 code
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY: //@1
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
}
Finally, the following two bean s will be registered in the spirng container
AutoProxyRegistrar
ProxyTransactionManagementConfiguration
Let's take a look at the code of these two classes.
AutoProxyRegistrar
This class implements the ImportBeanDefinitionRegistrar interface. There is a method registerBeanDefinitions in this interface. The spring container will call this method during startup. Developers can do some bean registration in this method. The main thing autoproxyregister does in this method is the following @1 code. You can click to have a look, I won't click here. The function of this code is to create a very key bean in the container: infrastructure advisor autoproxycreator. This class was introduced in aop earlier. It is a bean post processor, which will intercept the creation of all beans and create agents for qualified beans.
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
for (String annType : annTypes) {
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (candidate == null) {
continue;
}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
candidateFound = true;
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);//@1
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
}
}
To put it simply: the role of autoproxyregister is to enable the function of spring aop and create proxies for qualified bean s.
ProxyTransactionManagementConfiguration
@Configuration(proxyBeanMethods = false)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
//Registered bean: transaction Advisor (interceptor chains in spring aop are advisor objects)
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource,
TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
//Set transaction interceptor
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
//Set the order of transaction interceptors in aop
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
//Registered bean:TransactionAttributeSource, which is used to obtain transaction attribute configuration information: TransactionAttributeSource
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() { //@1
return new AnnotationTransactionAttributeSource();
}
//Registered bean: transaction interceptor
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(
TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
//Set the transaction manager in the interceptor, and txManager can be empty
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
It is a configuration class. The code is relatively simple. Three bean s are registered. The most important thing is to add a transaction Interceptor: TransactionInterceptor.
Autoproxyregister is responsible for enabling the function of aop, while ProxyTransactionManagementConfiguration is responsible for adding Transaction interceptors in aop. The combined effect of the two is to create a proxy object for the bean marked @ Transaction, and the Transaction management function is realized in the proxy object through the TransactionInterceptor interceptor.
Take another look at code @1, which registers a bean of TransactionAttributeSource type
TransactionAttributeSource interface source code:
public interface TransactionAttributeSource {
/**
* Determines whether the given class is a candidate class for transaction attributes in this TransactionAttributeSource metadata format.
* If this method returns false, the method on the given class is not traversed for getTransactionAttribute introspection.
* Therefore, returning false is an optimization of the unaffected class, while returning true only means that the class needs to fully introspect each method on a given class.
**/
default boolean isCandidateClass(Class<?> targetClass) {
return true;
}
//Returns the transaction property of the given method, or null if the method is non transactional.
TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);
}
The getTransactionAttribute method is used to obtain the transaction attribute information on the specified method. TransactionAttribute is familiar with TransactionDefinition. It is used to configure the transaction attribute information. TransactionAttribute inherits the TransactionDefinition interface. The source code is as follows, and two new methods are defined in TransactionAttribute, One method is used to specify the name of the transaction manager bean, and the other is used to determine whether a given exception needs to roll back the transaction
public interface TransactionAttribute extends TransactionDefinition {
//bean name of the transaction manager
@Nullable
String getQualifier();
//Judge whether the specified exception needs to roll back the transaction
boolean rollbackOn(Throwable ex);
}
The TransactionAttributeSource interface has an implementation class AnnotationTransactionAttributeSource, which is responsible for resolving @ Transaction into TransactionAttribute objects. You can set breakpoints in this class to see the search order of @ Transaction annotations, so that you can deeply understand where @ Transaction is placed to make transactions take effect.
AnnotationTransactionAttributeSource is most internally delegated to the SpringTransactionAnnotationParser#parseTransactionAnnotation method to parse the @ Transaction annotation, and then obtain the Transaction attribute configuration information: RuleBasedTransactionAttribute. The code is as follows:
org.springframework.transaction.annotation.SpringTransactionAnnotationParser
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
//Rollback rule
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
Let's focus on the transaction interceptor.
6,TransactionInterceptor
It is responsible for intercepting the execution of the @ Transaction method, starting the spring Transaction before the method is executed, and committing or rolling back the Transaction after the method is executed.
Before talking about the source code of this class, let's ask a few questions. Let's look at the code with questions and understand it better.
1. How is the transaction manager obtained?
2. When will a transaction commit?
3. What exception causes transaction rollback?
6.1. invokeWithinTransaction method
This method is the entrance to the transaction interceptor. Business methods that need spring to manage transactions will be intercepted by this method. You can set breakpoints to track them
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource();
//@6-1: get transaction attribute configuration information: get transaction attribute configuration information by parsing @ transaction annotation through TransactionAttributeSource.getTransactionAttribute
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//@6-2: get transaction manager
final TransactionManager tm = determineTransactionManager(txAttr);
//Convert transaction manager tx to PlatformTransactionManager
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// createTransactionIfNecessary is internal, which is not mentioned here. It mainly uses spring transaction hard coding to start the transaction, and finally returns a TransactionInfo object
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
// Business method return value
Object retVal;
try {
//Calling the next interceptor in aop will eventually call the business target method and obtain the return value of the target method
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//6-3: how to go under abnormal conditions? It may only need to commit or rollback, depending on the transaction configuration
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//Clean up transaction information
cleanupTransactionInfo(txInfo);
}
//6-4: after the business method returns, you only need to commit the transaction
commitTransactionAfterReturning(txInfo);
//Return execution result
return retVal;
}
}
6.2. Get transaction manager
//@6-2: get transaction manager
final TransactionManager tm = determineTransactionManager(txAttr);
The source code of determineTransactionManager is as follows:
org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
// txAttr == null || this.beanFactory == null , Returns the transaction manager configured in the interceptor
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
//qualifier is the bean name of the transaction manager specified by value or transaction manager in the @ Transactional annotation
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
//Find the bean of [beanName:qualifier,type:TransactionManager] from the spring container
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
//Find the bean of [beanName:this.transactionManagerBeanName,type:TransactionManager] from the spring container
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
else {
//Finally, find the transaction manager in the spring container through the transaction manager type
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
As can be seen from the above, the search order of the transaction manager is:
1. First, check whether the transaction manager is specified in @ Transactional through value or transaction manager
2. Does TransactionInterceptor.transactionManagerBeanName have a value? If so, the transaction manager will be found through this value
3. If there are neither of the above, the transaction manager of transaction manager type will be found from the spring container
6.3. How to go under abnormal circumstances?
try{
//....
}catch (Throwable ex) {
//6-3: how to go under abnormal conditions? It may only need to commit or rollback, depending on the transaction configuration
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
It can be seen from the source code that if an exception occurs, the completeTransactionAfterThrowing method will be entered. The source code of completeTransactionAfterThrowing is as follows
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
//@6-3-1: judge whether the transaction needs to be rolled back
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
//Rollback transaction through transaction manager
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
else {
//Commit a transaction through the transaction manager
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
}
Pay attention to the above @6-3-1 code to determine whether the transaction needs to be rolled back. The transactionAttribute.rollbackOn(ex) is called, and finally the following method will be entered
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
//@In Trasaction, you can specify the list of exceptions that need to be rolled back through rollback for, and specify the exceptions that do not need to be rolled back through noRollbackFor attribute
//Judge whether the ex type exception needs to be rolled back according to the rollback rules specified in @ Transactional
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
//If there is no match in the @ Transactional annotation, the default rule will be followed and it will be judged through super.rollbackOn
if (winner == null) {
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
The source code of super.rollbackOn(ex) is as follows. It can be seen that by default, the transaction will be rolled back only when the exception type is RuntimeException or Error.
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
6.4. How to go without abnormality?
//6-4: after the business method returns, you only need to commit the transaction
commitTransactionAfterReturning(txInfo);
If there is no exception, the commitTransactionAfterReturning method will be entered. The commitTransactionAfterReturning source code is as follows. It is relatively simple to call the commit method of the transaction manager to submit the transaction
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
The source code analysis is almost complete. It is recommended that you set breakpoints to track and deepen your understanding of the transaction principle.
7. Key review
1. When using @ Transaction, do not forget the @ EnableTransactionManagement annotation, otherwise the Transaction will not work
2. The function of @ Transaction is mainly realized through aop, and the key code is in the Transaction interceptor interceptor
3. By default, transactions will only be rolled back under RuntimeException or Error exceptions. You can configure other exception types that need to be rolled back or do not need to be rolled back through @ Transaction
8. Classroom discussion
What is the execution result of the following code? Why?
@Component
public class ServiceA {
@Autowired
ServiceB serviceB;
@Transactional
public void m1() {
try {
serviceB.m2();
} catch (Exception e) {
e.printStackTrace();
}
"insert into t_user (name) values ('Zhang San')";
}
}
@Component
public class ServiceB {
@Transactional
public void m2() {
"insert into t_user (name) values ('Li Si')";
throw new RuntimeException("Throw exception manually!");
}
}
Welcome to leave a message and share your thoughts with me. If there is any harvest, you are also welcome to share this article with your friends.
Source: https://mp.weixin.qq.com/s?__ biz=MzA5MTkxMDQ4MQ==&mid=2648937715&idx=2&sn=2d8534f9788bfa4678554d858ec93ab3&scene=21#wechat_ redirect