The problem of coexistence of spring declaration transaction and annotation transaction

   There are always two types of transaction management in spring: declaration transaction and annotation transaction. When spring implements it, it will register two TransactionInterceptors respectively.

  That is to say, in the implementation of transaction interception, for a method, there will be two TransactionInterceptors to intercept the transaction independently. The priority of the two transaction processing is related to the order, and whether to generate a nested transaction is related to the propagation.
   But in the project I built, the ideal state is that both transaction declarations are best executed by one TransactionInterceptor, and the annotation transaction has the highest priority. That is to say, if a method declares @Transactional, then spring will ignore the declaration transaction, and the annotation transaction shall prevail. However, I searched Baidu stackovervlow and did not see a similar solution. In fact, the reason for this is also very boring: two TransactionInterceptors feel uncomfortable, obviously one can be done, why do you need two?
   Analyze the code, in the spring-transaction source code, the class AnnotationDrivenBeanDefinitionParser (responsible for parsing the tx:annotation-driven tag),
	// Create the TransactionInterceptor definition.
				RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
				interceptorDef.setSource (eleSource);
				interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				registerTransactionManager(element, interceptorDef);
				interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
				String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

  It can be seen that spring does not determine whether a TransactionInterceptor already exists, but directly declares a TransactionInterceptor. Spring does the same with declarative transactions.
 

Continue to analyze the TransactionInterceptor source code.
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
			throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
		final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass);

  Continue to analyze getTransactionAttributeSource().getTransactionAttribute(), we can know that spring's decision on transaction is NameMatchTransactionAttributeSource in the declaration transaction, and AnnotationTransactionAttributeSource in the annotation transaction.
  This is well understood. NameMatchTransactionAttributeSource matches the declared transaction relationship according to the method name; and AnnotationTransactionAttributeSource is based on annotations.
  As mentioned earlier, the declaration transaction and the annotation transaction coexist, and the annotation transaction has the highest priority, so in terms of code implementation, the AnnotationTransactionAttributeSource and the NameMatchTransactionAttributeSource coexist, and AnnotationTransactionAttributeSource>NameMatchTransactionAttributeSource.
  Is there any way? In fact, spring provides this way of implementation, continue to search, and found that in TransactionInterceptor, there is such a code
	public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) {
		this.transactionAttributeSource = new CompositeTransactionAttributeSource(transactionAttributeSources);
	}

   As shown in the code, TransactionInterceptor actually supports injecting multiple TransactionAttributeSources.
Continue to analyze CompositeTransactionAttributeSource
	@Override
	public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
		for (TransactionAttributeSource tas : this.transactionAttributeSources) {
			TransactionAttribute ta = tas.getTransactionAttribute(method, targetClass);
			if (ta != null) {
				return ta;
			}
		}
		return null;
	}

  If there are multiple TransactionAttributeSources, loop; until the transaction attribute of one of the TransactionAttributeSources is not empty, the loop is interrupted and returns.

After the above analysis, the solution is clear, that is: the system maintains only one TransactionInterceptor responsible for transaction interception, and this TransactionInterceptor must hold AnnotationTransactionAttributeSource and NameMatchTransactionAttributeSource.

  There are two solutions:
  1. Do not use tx:annotation-driven and tx:advice in the project, but declare TransactionInterceptor by yourself, then inject AnnotationTransactionAttributeSource and NameMatchTransactionAttributeSource, and finally implement transaction interception by configuring aop:config.
  2. Keep tx:advice and delete tx:annotation-driven. Create a spring listener, and after the spring is loaded, make changes to the TransactionInterceptor, the code is as follows
  @Component
public class AppContextListener  implements ApplicationListener<ContextRefreshedEvent>{

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		TransactionInterceptor  interceptor  = (TransactionInterceptor)event.getApplicationContext().getBean(TransactionInterceptor.class);
		interceptor.setTransactionAttributeSources(new TransactionAttributeSource[]{new AnnotationTransactionAttributeSource(),interceptor.getTransactionAttributeSource()});
	
	}

}

  Declare tx:advice, and spring will register a TransactionInterceptor by itself. At the same time, the TransactionAttributeSource of the TransactionInterceptor is NameMatchTransactionAttributeSource. Therefore, you only need to add AnnotationTransactionAttributeSource in the first array.
In this way, only one TransactionInterceptor is used for transaction interception, and the transaction priority is annotated > declaring transaction
 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326942396&siteId=291194637