Spring Transactions Article 4 - Spring Transactions All you want to know is here

Catalog

Preface

InfrastructureAdvisorAutoProxyCreator

How Spring generates proxy objects for transactions

Why only AOP's post-processor is preserved when both transaction and AOP are opened

What is the problem with a method being enhanced by both transaction and AOP?

Why did the transaction fail?

How do I resolve transaction failures when both transactions and AOP are open?

summary

Preface

The previous articles explored how transaction methods can be intercepted and enhanced. But let's talk about AOPI also talked about how to enhance an object by first generating a proxy object and then finding the interceptor chain of the enhanced object, so that the interceptor methods can be enhanced one by one. In addition, this article will answer the question mentioned above, what happens when AOP and transaction are open at the same time?

InfrastructureAdvisorAutoProxyCreator

* As we have already analyzed earlier, this postprocessor will be introduced via @EnableTransactionManagement to see its inheritance structure

(Believed that peers who have seen AOP will be familiar with the AbstractAutoProxyCreator and Smart InstantiationAwareBeanPostProcessor post processors here, so the principle of enhanced AOP enhancement for transaction objects is consistent. Not much is described here, and can be moved if needed.) Spring AOP Part 2 - How Spring parses facets to get tangent points, notify and generate proxy objects , I believe after reading that article, you will know what its parents are doing.

How Spring generates proxy objects for transactions

In the previous article, we introduced the enhanced invoke method for transactional methods, but to invoke the enhanced method, a proxy object and interceptor chain are needed first. Let's take a look at how Spring generates proxy objects for transactional objects and how to obtain interceptor chains. First, we look at the flowchart for generating proxy objects and see that the link can be moved indefinitely. Transaction Generation Agent Flowcharthttps://www.processon.com/view/link/614ebc5e0791290c0c42a96ahttps://www.processon.com/view/link/614ebc5e0791290c0c42a96a

(To briefly explain the above process, transactions introduce an Infrastructure AdvisorAutoProxyCreator post processor, which can then be used to find and match advisors with its parent class, grandparents, etc. The previous article mentioned that transactions introduce a BeanFactoryTransactionAttributeSourceAdvisor type advisor which implements the Advisor interface, so in the diagramThis step of advisorRetrievalHelper.findAdvisorBeans() finds the advisor, which relies on TransactionAttributeSource for bean matching, which is based on classFilter and methodMatcher.This is how findAdvisorsThatCanApply determines if an advisor matches the current bean. If it does, it means that @Transactional has been written on the class or on a method on the class.Comment to create a dynamic proxy. The process of creating a dynamic proxy from a proxy object and an interceptor chain has been described in the previous article, so it will not be expanded here.

Why only AOP's post-processor is preserved when both transaction and AOP are opened

* Let's take another look at the flow chart of AOP's creation proxy object, and we can't see if we can step through the links. AOP Generation Proxy Object Flowcharthttps://www.processon.com/view/link/614ec7e0e0b34d7b343306e8https://www.processon.com/view/link/614ec7e0e0b34d7b343306e8

The AOP process of creating proxy objects is similar to that of transactions, except that when querying advisor instances, AnnotationAwareAspectJAutoProxyCreator, a postprocessor introduced when AOP was opened, overrides the findCandidateAdvisors method, which obtains advisors that implement the Advisor interface by calling the parent class super.findCandidateAdvisors().(Transactions also use this method to get advisors they need)So what Infrastructure Advisor AutoProxyCreator can do is AnnotationAwareAspectJAutoProxyCreator, so when two conflicts occur, just keep AnnotationAwareAspectJAutoProxyCreator.

Since the AnnotationAwareAspectJAutoProxyCreator feature fully covers the Infrastructure AdvisorAutoProxyCreator, only AOP is turned on.Don't start a transaction, that is, just add @EnableAspectJAutoProxy instead of @EnableTransactionManagementLine? The answer is no, of course, because @EnableTransactionManagement introduces not only a post-processor, but also an advisor instance and TransactionAttributeSource, which are the key to the transaction.

Since AnnotationAwareAspectJAutoProxyCreator is so good, can't you just introduce it when you start a transaction? Why should you choose it when you're in conflict with AOP? I think this design might be because it's fairly clear, because transaction enhancements are relatively simple, just look for @Transactional on the method or class, andAOP is complex, different points of tangency, possibly only different parameter types will affect entry. So the implementation process is complex, and introducing some judgment about AOP if you just open transactions will undoubtedly result in some performance consumption. Separate management of transactions and AOP also reduces coupling.

What is the problem with a method being enhanced by both transaction and AOP?

//Face class
@Aspect
public class QueryAspect {
    //Tangent Point
    @Pointcut("execution(void com.transaction.service.DepositService.deposit(Long, Integer))")
    public void pointCut(){};

    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) throws Exception {
        System.out.println("Before calling the target method @Before Parameter information can be obtained in advance:" + Arrays.toString(joinPoint.getArgs()));
        //Error simulating before
        //int a = 1 / 0;
    }

    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        System.out.println("After calling the target method @After");
        // Simulate After exception
        //int a = 1 / 0;
    }

    @AfterReturning(returning = "result", pointcut = "pointCut()")
    public void methodAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("After the target method returns @AfterReturning, Return results are available " + result);
        //Error simulating AfterReturning
        //int a = 1 / 0;
    }

    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void methodAfterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("After throwing an exception, @AfterThrowing " + ex);
        //Simulate @AfterThrowing exception
        //int a = 1 / 0;
    }

    @Around(value = "pointCut()")
    public Object methodAround(ProceedingJoinPoint pdj) throws Throwable {
        Object result = null;
        System.out.println("Before calling the target method @Around ");
        try {
            //Call target method
            result = pdj.proceed();
        } catch (Throwable ex) {
            System.out.println("Exception information caught:" + ex);
        }
        System.out.println("After calling the target method @Around ");
        return result;
    }
}

//Configuration Class
@EnableTransactionManagement
@ComponentScan({"com.transaction.*", "com.proxy"})
@EnableAspectJAutoProxy
public class TransactionConfig {

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/transaction?useSSL=false");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(20);
        dataSource.setInitialSize(10);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public QueryAspect queryAspect() {
        return new QueryAspect();
    }
}

//Business Interface Class
public interface DepositService {
    
    public void deposit(Long userId, Integer money);
}

//Business implementation class
@Service
public class DepositServiceImpl implements DepositService {

    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional
    public void deposit(Long userId, Integer money) {
        accountDao.updateAccountInfos(userId, money);
        //Analog transaction error
       int a = 1 / 0;
    }
}

//Data Access Layer
@Repository
public class AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED)
    public boolean updateAccountInfos(Long userId, Integer money) {
        String sql = "update AccountInfo set balance = balance + " + money + " where id = " + userId;
        jdbcTemplate.update(sql);

        // Simulate Transaction Exceptions
        //int a = 1 / 0;
        return true;
    }
}

//Test Class
public class TransactionMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TransactionConfig.class);
        DepositService depositService = (DepositService) applicationContext.getBeanFactory().getBean("depositServiceImpl");
        depositService.deposit(1L, 1);
    }
}
<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>


    </dependencies>

The result of executing the above code is:

Before calling the target method @Around 
Before calling the target method @Before Parameter information can be obtained in advance:[1, 1]
After throwing an exception, @AfterThrowing java.lang.ArithmeticException: / by zero
 After calling the target method @After
 Exception information caught: java.lang.ArithmeticException: / by zero
 After calling the target method @Around

From the execution results, both @AfterThrowing and @Around caught the exception when the target method exception occurred, but the transaction did not take effect, that is, the database was updated and no rollback was performed, that is, the transaction failed.

Why did the transaction fail?

* As we have previously analyzed, the advisors array is sorted so that the order of transaction interceptors is executed before the AOP interceptor. First, we need to analyze the process of interceptor execution. The previous article has analyzed the order of AOP interceptor execution in detail, with the following connections

Spring AOP - Order of execution of notification methodshttps://blog.csdn.net/paralysed/article/details/120478652?spm=1001.2014.3001.5501https://blog.csdn.net/paralysed/article/details/120478652?spm=1001.2014.3001.5501Spring AOP-Proxy Object Enhancement Target Method's Process to Notify Method Execution Orderhttps://blog.csdn.net/paralysed/article/details/120489042?spm=1001.2014.3001.5501https://blog.csdn.net/paralysed/article/details/120489042?spm=1001.2014.3001.5501 (2) Re-analyze the ordering of advisors in conjunction with transactions. The type of advisor introduced by transactions is BeanFactoryTransactionAttributeSourceAdvisor, which inherits the Advisor interface and therefore places the advisor in the advisor array before the AOP advisor, so the advisor of the transaction precedes the AOP transaction after the first ordering of the advisor array because the transaction precedes the AOP transaction.Both advisors and AOP advisors are of the Ordered type, so the second sorting of the advisor array depends primarily on who has configured the higher order priority. AOP advisors can change the execution order of all their interceptors by adding the @Order annotation to the facet class, while transactions can be specified when they are opened, such as @EnableTransactionManagement (order = 100)In the previous article, we analyzed that the default order for AOP is Ordered.LOWEST_PRECEDENCE, and click on the EnableTransactionManagement comment to see int order() default Ordered.LOWEST_PRECEDENCE;This line of code represents the default and lowest priority. So if neither AOP nor transaction specifies an order, the order of the advisor list does not actually change after the second step of sorting. Then the order of advisor execution becomes transactional advisor->Around->Before->After->AfterReturning->AfterThrowing, and the flow of interceptor execution becomes as follows.

try {
	try {

		// @Around notifies enhanced logic that can be added before the target method is called actively

		//Execute @Before notification method
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		try {
				try {
					//Execute Target Method
					return invokeJoinpoint();
				} catch (Throwable ex) {
					if (shouldInvokeOnThrowing(ex)) {
						//Execute @AfterThrowing notification method
						invokeAdviceMethod(getJoinPointMatch(), null, ex);
					}
					throw ex;
				}
				//Execute @AfterReturning notification method
				this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		} finally {
			//Execute @After notification method
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		} 

		// @Around tells you to call the target method before continuing
		
	} catch (IllegalArgumentException ex) {
		throw new AopInvocationException("Mismatch on arguments to advice method [" +
				this.aspectJAdviceMethod + "]; pointcut expression [" +
				this.pointcut.getPointcutExpression() + "]", ex);
	} catch (InvocationTargetException ex) {
		throw ex.getTargetException();
	}
} catch (Throwable ex) {
	// Rollback operation after transaction catches exception
	completeTransactionAfterThrowing(txInfo, ex);
	throw ex;
} finally {
	cleanupTransactionInfo(txInfo);
}

However, the around notification method enhancement logic in this example catches exceptions for the target method, which is equivalent to catching exceptions for all interceptors. After completing the around logic, the following execution process should follow

try {
	try {

		Object result = null;
        System.out.println("Before calling the target method @Around ");
        try {
			//Execute @Before notification method
			this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
			try {
					try {
						//Execute Target Method
						return invokeJoinpoint();
					} catch (Throwable ex) {
						if (shouldInvokeOnThrowing(ex)) {
							//Execute @AfterThrowing notification method
							invokeAdviceMethod(getJoinPointMatch(), null, ex);
						}
						throw ex;
					}
					//Execute @AfterReturning notification method
					this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
			} finally {
				//Execute @After notification method
				invokeAdviceMethod(getJoinPointMatch(), null, null);
			} 
		} catch (Throwable ex) {
            System.out.println("Exception information caught:" + ex);
        }
        System.out.println("After calling the target method @Around ");
        //If there are no exceptions during execution, the result of execution is returned and the rollback of the transaction is not performed
        return result;
	} catch (IllegalArgumentException ex) {
		throw new AopInvocationException("Mismatch on arguments to advice method [" +
				this.aspectJAdviceMethod + "]; pointcut expression [" +
				this.pointcut.getPointcutExpression() + "]", ex);
	} catch (InvocationTargetException ex) {
		throw ex.getTargetException();
	}
} catch (Throwable ex) {
	// Rollback operation after transaction catches exception
	completeTransactionAfterThrowing(txInfo, ex);
	throw ex;
} finally {
	cleanupTransactionInfo(txInfo);
}

The reason why the transaction fails is that the enhanced method of the custom Around captures the exception thrown by the target method or any interceptor, but it does not throw the exception, so the transaction cannot catch the exception and cannot roll back naturally.

How do I resolve transaction failures when both transactions and AOP are open?

We already know why the transaction failed, so how do we resolve it?

One way to do this is to start with a custom Around enhancement method, which now swallows exceptions, so you can throw ex after it catches them and throw them outward, like @AfterThrowing In the same way, the transaction can catch the exception. Or, instead of catching the exception in this notification method, let the outside transaction handle it.

The first method solves the problem, but there are also some inappropriate situations, such as sometimes you don't want to throw an exception, because if the exception can't be handled outside, it can cause program interruptions or other more difficult problems.

The more appropriate method should be to have the transaction interceptor directly try to catch the exception of the target method, which is the following execution process

try {

	Object result = null;
    System.out.println("Before calling the target method @Around ");
    try {
		//Execute @Before notification method
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		try {
				try {
					//The try catch of a transaction executes at the innermost level
					try {
						//Execute Target Method
						return invokeJoinpoint();
					} catch (Throwable ex) {
						// Rollback operation after transaction catches exception
						completeTransactionAfterThrowing(txInfo, ex);
						throw ex;
					} finally {
						cleanupTransactionInfo(txInfo);
					}
				} catch (Throwable ex) {
					if (shouldInvokeOnThrowing(ex)) {
						//Execute @AfterThrowing notification method
						invokeAdviceMethod(getJoinPointMatch(), null, ex);
					}
					throw ex;
				} 
				//Execute @AfterReturning notification method
				this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		} finally {
			//Execute @After notification method
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		} 
	} catch (Throwable ex) {
        System.out.println("Exception information caught:" + ex);
    }
    System.out.println("After calling the target method @Around ");
    //If there are no exceptions during execution, the result of execution is returned and the rollback of the transaction is not performed
    return result;
} catch (IllegalArgumentException ex) {
	throw new AopInvocationException("Mismatch on arguments to advice method [" +
			this.aspectJAdviceMethod + "]; pointcut expression [" +
			this.pointcut.getPointcutExpression() + "]", ex);
} catch (InvocationTargetException ex) {
	throw ex.getTargetException();
}

In this way, transactions can get first-hand information without worrying about other interceptors swallowing exceptions. But how do you get the transaction interceptor method to execute after the target method, that is, to put the transaction advisor last when sorting, in which case the advisorThe execution order becomes Around->Before->After->AfterReturning->AfterThrowing->Transaction advisor. Then the execution method of the interceptor becomes what we want. So to do this, you have to do something with the advisor sort. The first sort can't be changed, so you can only use the second step to set the order by setting it up.Change the order, but since the default order is Integer.MAX_VALUE, you can only choose to reduce the order value of the AOP advisor (the smaller the number, the higher the priority) if you want the transaction method to be last.You can't increase the transaction advisor. Or you can set both order values to ensure that the transaction's order value is large. So the easiest way is to add @Order(1) to the facet class, but you can also use no 1, as long as it's smaller than Integer.MAX_VALUE.

summary

This article discusses transaction enhancements (i.e., rollbacks) to the target methodThe process also analyses some problems that may be encountered when transaction and AOP are open at the same time. It may also be because, after all, Around Enhanced Logic is written by us, it will not invalidate the transaction as long as the exception is not swallowed. However, we also provide a solution for transaction invalidation, which can be noted when using it.

Tags: Java MySQL Spring

Posted on Mon, 27 Sep 2021 12:01:05 -0400 by lutzlutz896