Preface
Previous article:
Use of Spring-jdbc and 8 ways of managing Spring transactions (declarative + programmatic)
Introduces many ways to use Spring transactions, including when it comes to the full comment @Transactional approach, so this article takes the @Transactional approach as a starting point to unravel the mystery of Spring transaction management~
Fully annotated @Transactional Spring transactions
SpringBoot is popular today, and the use of the Spring Framework based on XML configuration is doomed to be a thing of the past.
Annotation-driven applications, metadata-oriented programming, have become a growing preference for developers, and its benefits are self-evident.
Use of @Transactional
1. Turn on Annotation Driver
@EnableTransactionManagement // Turn on Annotation Driver @Configuration public class JdbcConfig { ... }
Before using the @EnableTransactionManagement annotation, be sure you have configured at least one Bean for PlatformTransactionManager
2. Label the @Transactional annotation on the method (or class (interface) that you want to join the transaction
@Service public class HelloServiceImpl implements HelloService { @Autowired private JdbcTemplate jdbcTemplate; @Transactional @Override public Object hello() { // Insert a record into the database String sql = "insert into user (name,age) values ('fsx',21)"; jdbcTemplate.update(sql); // Doing the rest may throw an exception System.out.println(1 / 0); return "service hello"; } }
Unit Test:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, JdbcConfig.class}) public class TestSpringBean { @Autowired private HelloService helloService; @Test public void test1() { System.out.println(helloService.getClass()); //class com.sun.proxy.$Proxy29 helloService.hello(); } }
It's that simple, and the transaction takes effect.
Especially from the usage steps, like @Async.In fact, it was very similar to it, so we strongly recommend that you consult the blog first:
[Home Spring] Use and principles of Spring asynchronous processing @Async, source analysis (@EnableAsync)
Next, you'll analyze the principle of annotation-driven transactions, starting with @EnableTransactionManagement ~~
@EnableTransactionManagement
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
Don't be too familiar, just like the @EnableAsync comment.The difference is just this @Import class, but we can still see that it's also an ImportSelector
TransactionManagementConfigurationSelector
The package it is in is org.springframework.transaction.annotation, and the jar belongs to: spring-tx (this jar is automatically imported if spring-jdbc is introduced)
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { // Obviously, for the most part, we don't use ~~~~~of AspectJ's static proxy // Two classes will be imported here ~~ case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); } }
Still, you can see that the same Async Configuration Selector that @EnableAsync imported inherits from AdviceModel ImportSelector, which is why I strongly recommend that you read the blog post about @Async first, since the pattern is the same ~
The three subclasses that AdviceModeImportSelector currently knows are AsyncConfiguration Selector, TransactionManagementConfiguration Selector, and CachingConfiguration Selector.This courseware will also focus on Spring's cache system @EnableCaching, and the pattern is very similar ~~
AutoProxyRegistrar
From the name means: automatic proxy registrar.It is an ImportBeanDefinitionRegistrar and it is recommended that you register Bean's definition information with the container yourself
// @since 3.1 public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; // It's important to note here that you get all the annotation types ~~instead of just @EnableAspectJAutoProxy // Reason: Because attributes such as mode, proxyTargetClass directly affect how the proxy works, at least there are annotations with these attributes: // @EnableTransactionManagement, @EnableAsync, @EnableCaching, etc. ~~~ // There is even an AOP-enabled comment: @EnableAspectJAutoProxy also sets the value of the property `proxyTargetClass', which has an associated effect~ Set<String> annoTypes = importingClassMetadata.getAnnotationTypes(); for (String annoType : annoTypes) { AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType); if (candidate == null) { continue; } // Get these two attributes in the note // Note: If you're like @Configuration or something else, they're null Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); // If a mode l exists and the proxyTargetClass attribute exists // And the class types of the two attributes are also correct before they come in (so the rest of the notes are blocked out ~) if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { // Flag: Candidate comment found ~~~ candidateFound = true; if (mode == AdviceMode.PROXY) { // This is a very important ~~~to get back to the familiar AopConfigUtils tool class and the familiar registerAutoProxyCreatorIfNecessary method // It is primarily registered as an `internalAutoProxyCreator', but if it occurs multiple times, it is not in the form of overrides, but primarily for the first time // Of course, there's something inside it like making a grade increase, which has been analyzed before ~~~ AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); // See if you want to enforce CGLIB (you can see that this property is overridden if it occurs more than once) if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } // If none were found (I thought, how could it be swollen?) // It's possible: inject the class yourself, not with annotations (but that's not recommended) if (!candidateFound && logger.isInfoEnabled()) { // Output info log (note not error log) } } }
The most important thing about this step is to inject an automatic proxy Creator into the Spring container: org.springframework.aop.config.internalAutoProxyCreator and see if you're using a CGLIB or JDK proxy.
Tracking the source of AopConfigUtils, you will find that the transaction block injects an Infrastructure AdvisorAutoProxyCreator into the container, which reads the Advisor class and proxies the matching bean s twice.Detailed description in the Spring AOP blog, skipped here~
Reference resources: Core class of Spring AOP: AbstractAdvisorAutoProxy AutoProxy Proxy Creator Deep Profile (AnnotationAwareAspectJAutoProxyCreator)
ProxyTransactionManagementConfiguration
It's a @Configuration, so see what beans it pours into the container
@Configuration public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { // This Advisor is at the heart of a transaction...It is also the object that this article focuses on @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); advisor.setAdvice(transactionInterceptor()); // The order is specified by the Order property of the @EnableTransactionManagement annotation with the default value: Ordered.LOWEST_PRECEDENCE if (this.enableTx != null) { advisor.setOrder(this.enableTx.<Integer>getNumber("order")); } return advisor; } // TransactionAttributeSource is a class that resembles the design pattern of `TargetSource` // The AnnotationTransactionAttributeSource annotation-based transaction attribute source ~~is used directly here @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } // Transaction Interceptor, which is a `MethodInterceptor', is also the core part of Spring's transaction processing // Note: You can define a TransactionInterceptor yourself (with the same name) to override this Bean (note that overrides) // Also note that your custom BeanName must have the same name, that is, it must be named: transactionInterceptor otherwise both will be registered in the container ~~~ @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new TransactionInterceptor(); // Transaction Properties interceptor.setTransactionAttributeSource(transactionAttributeSource()); // Transaction Manager (that is, the transaction manager that annotates the final need to use, the parent class is already processed) // Note here: It is advisable that we do not need to specify anything special, and eventually it will go to the container and match a suitable ~~~ if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; } } // Parent class (abstract class) It implements the ImportAware interface so it gets all the annotation information for the @Import class @Configuration public abstract class AbstractTransactionManagementConfiguration implements ImportAware { @Nullable protected AnnotationAttributes enableTx; /** * Default transaction manager, as configured through a {@link TransactionManagementConfigurer}. */ // Here: Note the default transaction processor (custom configuration may be recommended by implementing the interface TransactionManagementConfigurer) // Because Transaction Manager is something that generally works globally, Spring also provides the ability to customize ~~ @Nullable protected PlatformTransactionManager txManager; @Override public void setImportMetadata(AnnotationMetadata importMetadata) { // Here: Just get the @EnableTransactionManagement comment and save it as an AnnotationAttributes this.enableTx = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); // This note is necessary ~~~~~~~~~~~~ if (this.enableTx == null) { throw new IllegalArgumentException("@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); } } // The configuration file implements this interface as @Async does here.Then give the annotation driven to a default transaction manager ~~~ // Design patterns are all thoughtful ~~ @Autowired(required = false) void setConfigurers(Collection<TransactionManagementConfigurer> configurers) { if (CollectionUtils.isEmpty(configurers)) { return; } // Similarly, you are allowed to configure at most one ~~ if (configurers.size() > 1) { throw new IllegalStateException("Only one TransactionManagementConfigurer may exist"); } TransactionManagementConfigurer configurer = configurers.iterator().next(); this.txManager = configurer.annotationDrivenTransactionManager(); } // Register a listener factory to support the @TransactionalEventListener annotation method for listening for transaction-related events // Later, we will discuss how to monitor transactions through event monitoring mode ~~~ @Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public static TransactionalEventListenerFactory transactionalEventListenerFactory() { return new TransactionalEventListenerFactory(); } }
Be sure to refer to:
[Home Spring] Spring transaction related basic class beaters (spring-jdbc and spring-tx jars), with an emphasis on AnnotationTransactionAttributeSource
The next big thing is the BeanFactoryTransactionAttributeSourceAdvisor, an enhancer
BeanFactoryTransactionAttributeSourceAdvisor
First, look at its parent: AbstractBeanFactoryPointcutAdvisor, which was previously explained extensively when it came to AOP:
[Home Spring] Advisor, Pointcut Advisor, IntroductionAdvisor, IntroductionInterceptor for Spring AOP (Introduction Enhancement)
The parent is an Advisor related to the Bean Factory.
Look at yourself again, it's an Advisor that has a relationship to both Bean factories and transactions
As you can see from the configuration class above, it's new.
Advices used are: advisor.setAdvice(transactionInterceptor()), which is the transaction interceptor within a container ~~~
The source of transaction attributes used is advisor.setTransactionAttributeSource(transactionAttributeSource()), which supports three transaction annotations to label ~~
// @since 2.5.5 // It's an AbstractBeanFactoryPointcut Advisor, please refer to the previous blog notes~~ public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private TransactionAttributeSource transactionAttributeSource; // This is very important, that is, the facet.It determines which classes will be cut in to generate proxy objects~ // About: TransactionAttributeSourcePointcut is described below~ private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { // Notice here that `getTransactionAttributeSource` is one of its abstract methods~~ @Override @Nullable protected TransactionAttributeSource getTransactionAttributeSource() { return transactionAttributeSource; } }; // Moveable to set a transaction property source manually~ public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { this.transactionAttributeSource = transactionAttributeSource; } // Of course, we can specify the ClassFilter default: ClassFilter classFilter = ClassFilter.TRUE; matches all classes public void setClassFilter(ClassFilter classFilter) { this.pointcut.setClassFilter(classFilter); } // Here pointcut is to use your own pointcut to cut in ~~ @Override public Pointcut getPointcut() { return this.pointcut; } }
Now of course, let's focus on how TransactionAttributeSourcePointcut cuts in
TransactionAttributeSourcePointcut
This is the matching Pointcut aspect of the transaction, which determines which classes need to generate proxy objects to apply the transaction.
// First its access default is shown for internal use // First it inherits from StaticMethodMatcherPointcut so `ClassFilter classFilter = ClassFilter.TRUE;`Match all classes // And isRuntime=false means that only static matching of methods is required ~~~ abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { // Static matching of methods is sufficient (since transactions do not need to dynamically match such fine-grained ~~) @Override public boolean matches(Method method, Class<?> targetClass) { // Subclasses that implement the following three interfaces do not need to be proxied for direct release // TransactionalProxy is a subclass of SpringProxy.If a Bean is produced by TransactionProxyFactoryBean, this interface will be automatically implemented, and it will not be proxied here again // PlatformTransactionManager:spring Abstract Transaction Manager~~ // PersistenceExceptionTranslator Conversion Interface for RuntimeException to DataAccessException if (TransactionalProxy.class.isAssignableFrom(targetClass) || PlatformTransactionManager.class.isAssignableFrom(targetClass) || PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) { return false; } // Important: Get the transaction property source ~~~~ // If tas == null indicates that there is no transaction attribute source configured, that is all matches, that is, all methods match ~~~ (which is still surprising to me) // Or annotated methods like @Transaction will match ~~ TransactionAttributeSource tas = getTransactionAttributeSource(); return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); } ... // Provided to me by subclasses, tell me the source of transaction attributes ~~~so I know which methods I need to work with ~~ @Nullable protected abstract TransactionAttributeSource getTransactionAttributeSource(); }
With respect to the matches method, as long as each Bean in the container passes through AbstractAutoProxyCreator#postProcessAfterInitialization, which calls the wrapIfNecessary method, so all methods of all beans in the container execute this matche method at container startup, so note the use of caches ~~~
Combine this with this post:
[Home Spring] Spring transaction related basic class beaters (spring-jdbc and spring-tx jars), with an emphasis on AnnotationTransactionAttributeSource
You will know which classes Spring will eventually generate proxy objects for, and how transactions will work ~~~
The real way to execute transactions is in the TransactionInterceptor, an enhancer, which, because of the complexity of the cost ratio, is detailed in a separate extract ~~
@Transactional simple explanation
This transaction annotation can be used on classes or methods.
- Marking a transaction annotation at the service component class level is equivalent to applying the annotation to each service method of the service component
- Transaction annotations are applied at the method level and are a more granular way of annotating transactions
Note: If both a method and the class to which the method belongs have transaction annotation properties, the transaction annotation properties of the method are preferred.
In addition, Spring supports three different transaction annotations:
- Spring Transaction Annotation org.springframework.transaction.annotation.Transactional (Pure Blood, Official Recommendation)
- JTA Transaction Annotation javax.transaction.Transactional
- EJB 3 Transaction Annotation javax.ejb.TransactionAttribute
Although the above three notes are semantically the same, they are not used in the same way. If you really want to make other notes, please note how they are used ~
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { // value and transactionManager properties mean the same thing.When multiple transaction managers are configured, this property can be used to specify which transaction manager to select. @AliasFor("transactionManager") String value() default ""; // @since 4.2 @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; //Propagation behavior of transactions Isolation isolation() default Isolation.DEFAULT; // Transaction isolation level int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; //Transaction timeout, default is -1 boolean readOnly() default false; //Whether read-only defaults to false // Exceptions that need to be rolled back can specify multiple exception types without specifying that only RuntimeException s and Error s should be rolled back by default Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; // Exceptions that do not require rollback~~ Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
TransactionManagementConfigurer, a custom annotation-driven transaction manager
The annotated transaction manager will have a default person, and then ~~ can also be formulated in @Transaction via the value attribute.
A very elegant way to change the default is to use the TransactionManagementConfigurer interface to provide:
@EnableTransactionManagement @Configuration public class JdbcConfig implements TransactionManagementConfigurer { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); return dataSourceTransactionManager; } // Replication provides one.You can either get one by yourself, or you can get ~~from the container in the same way. @Override public PlatformTransactionManager annotationDrivenTransactionManager() { // Direct use of transaction managers within containers~~ return transactionManager(dataSource()); } }
Think: If you have both @EnableAspectJAutoProxy and @EnableTransactionManagement, how does the automatic proxy creator inject into whom?Is it related to the order in which notes are labeled?
Based on the previous analysis and the analysis in this article, we know that:
- @EnableAspectJAutoProxy injects AnnotationAwareAspectJAutoProxyCreator like a container
- @EnableTransactionManagement injects Infrastructure AdvisorAutoProxyCreator like a container
When they are used together, they look like the following:
@EnableTransactionManagement @EnableAspectJAutoProxy @Configuration public class JdbcConfig { ... }
So which proxy will be injected to create the class eventually?
In fact, we've analyzed it before, and the core code is here: the AopConfigUtils#registerOrEscalateApcAsRequired method
public abstract class AopConfigUtils { ... @Nullable private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { // You can see here is a clever handling: automatic proxy creators will be upgraded ~~ // So if you came in for the first time with `Infrastructure Advisor AutoProxyCreator', and you came in for the second time with `AnnotationAwareAspectJAutoProxyCreator', you would get this Class for the second time // The opposite is not true.This is a priority order to maintain, referring specifically to the static code block of this class, which is the last `AnnotationAwareAspectJAutoProxyCreator` in the order. if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; } ... }
From the above analysis, you can see that no matter how many of these annotations you have or how they are sequenced, there is an internal priority-boosting mechanism to ensure downward coverage compatibility.So in general, we use the most advanced AnnotationAwareAspectJAutoProxyCreator, an automatic proxy creator ~~
Knowledge Exchange
If the group QR code fails, add a micro signal (or scan the QR code below): fsx641385712.
And note: the word "java joins the group" will be manually invited to join the group