Start with declarative transactions based on @Transactional full annotation, and thoroughly understand the principles of Spring transaction management

Posted by countcet on Wed, 15 May 2019 07:36:14 +0200

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:

  1. Spring Transaction Annotation org.springframework.transaction.annotation.Transactional (Pure Blood, Official Recommendation)
  2. JTA Transaction Annotation javax.transaction.Transactional
  3. 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

Topics: Spring JDBC Attribute SQL