(Transfer) Analysis of Spring Transaction Propagation Features - Confusion of Nested Transaction Method Calls - In-depth Good Article

Spring Transaction Propagation Mechanism Review 


   Spring transaction A widely spread statement is that one transaction method should not call another transaction method, otherwise two transactions will be generated. As a result, developers are tied down when designing transaction methods, for fear of accidentally stepping on a landmine. 
In fact, this is a misunderstanding caused by not knowing Spring's transaction propagation mechanism. Spring's support for transaction control is described in the TransactionDefinition class, which has the following important interface methods: 

  • int getPropagationBehavior(): The propagation behavior of the transaction
  • int getIsolationLevel(): The isolation level of the transaction
  • int getTimeout(): The expiration time of the transaction
  • boolean isReadOnly(): The read and write characteristics of the transaction



   Obviously, in addition to the propagation behavior of the transaction, other features of the transaction are accomplished by Spring with the help of the underlying resources, and Spring only acts as a proxy. However, the transaction propagation behavior is the function provided by Spring with its own framework, and it is the most precious gift that Spring provides to developers. The false statement tarnishes the most beautiful halo of the Spring transaction framework. 
   
   The so-called transaction propagation behavior is how when multiple transaction methods call each other, how the transaction is propagated among these methods. Spring supports the following 7 transaction propagation behaviors. 

  • PROPAGATION_REQUIRED: If there is no current transaction, a new transaction is created, and if a transaction already exists, it is added to the transaction. This is the most common choice.
  • PROPAGATION_SUPPORTS: The current transaction is supported, and if there is no current transaction, it will be executed in a non-transactional manner.
  • PROPAGATION_MANDATORY: Use the current transaction, throw an exception if there is no current transaction.
  • PROPAGATION_REQUIRES_NEW: Create a new transaction. If there is a current transaction, suspend the current transaction.
  • PROPAGATION_NOT_SUPPORTED: Perform the operation in a non-transactional manner, and suspend the current transaction if there is a current transaction.
  • PROPAGATION_NEVER: Executes in a non-transactional manner, throws an exception if there is currently a transaction.
  • PROPAGATION_NESTED: If a transaction currently exists, execute within a nested transaction. If there are no current transactions, do something similar to PROPAGATION_REQUIRED.



   Spring's default transaction propagation behavior is PROPAGATION_REQUIRED, which is suitable for most situations. If multiple ServiveX#methodX() work in a transactional environment (that is, they are all enhanced by Spring transactions), and the program has the following call chain: Service1 #method1()->Service2#method2()->Service3#method3(), then the three methods of these three service classes all work in the same transaction through Spring's transaction propagation mechanism. 

Nested service methods 
   

   Let's take a look at the example. The UserService#logon() method internally calls the UserService#updateLastLogon Time() and ScoreService#addScore() methods, both of which inherit from BaseService. The class structure between them is shown in the following figure:

   The UserService#logon() method internally calls the ScoreService#addScore() method, both of which have been transactionally enhanced through spring  AOP, so they work in the same transaction. Look at the specific code: 

 

package com.baobaotao.nestcall;  
@Service("userService")  
public class UserService extends BaseService {  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
  
    @Autowired  
    private ScoreService scoreService;  
      
    //①This method nests calls other methods of this class and methods of other service classes  
    public void logon(String userName) {  
        System.out.println("before userService.updateLastLogonTime...");  
        updateLastLogonTime(userName);//①-1 Other methods of this service class  
        System.out.println("after userService.updateLastLogonTime...");  
          
        System.out.println("before scoreService.addScore...");  
        scoreService.addScore(userName, 20); //①-2 Other methods of other service classes  
        System.out.println("after scoreService.addScore...");  
  
    }  
    public void updateLastLogonTime(String userName) {  
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";  
        jdbcTemplate.update(sql, System.currentTimeMillis(), userName);  
    }  

 

The Bean of ScoreService is injected into UserService, and the code of ScoreService is as follows: 

 

 

package com.baobaotao.nestcall;  
@Service("scoreUserService")  
public class ScoreService extends BaseService{  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
      
    public void addScore(String userName, int toAdd) {  
        String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";  
        jdbcTemplate.update(sql, toAdd, userName);  
    }  
}  

 


  Through Spring configuration, all public methods in ScoreService and UserService are added with Spring AOP transaction enhancement, so that UserService's logon() and updateLastLogonTime() and ScoreService's addScore() methods all work in a transactional environment. Here is the key configuration code: 

 

 

<?xml version="1.0" encoding="UTF-8" ?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"  
       xmlns:tx="http://www.springframework.org/schema/tx"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
    <context:component-scan base-package="com.baobaotao.nestcall"/>  
    <bean id="jdbcManager"  
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
          p:dataSource-ref="dataSource"/>  
  
    <!--①Add transaction enhancements to all public methods of all subclasses that inherit the BaseService class through the following configuration-->  
    <aop:config proxy-target-class="true">  
        <aop:pointcut id="serviceJdbcMethod"  
                      expression="within(com.baobaotao.nestcall.BaseService+)"/>  
        <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>  
    </aop:config>  
    <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">  
        <tx:attributes>  
            <tx:method name="*"/>  
        </tx:attributes>  
    </tx:advice>  
</beans>  

 


   Set the log level to DEBUG, start the Spring container and execute the UserService#logon() method, and carefully observe the following output logs: 

 

before userService.logon method...   
  
     //① Created a transaction   
Creating new transaction with name [com.baobaotao.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT   
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction   
Switching JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] to manual commit   
before userService.updateLastLogonTime...   
  
    <!--②updateLastLogonTime() and logon() are in the same Bean, and there is no addition to the existing transaction context.   
      action, but works "naturally" in the same transaction context -->   
Executing prepared SQL update   
Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]   
SQL update affected 1 rows   
after userService.updateLastLogonTime...   
before scoreService.addScore...   
  
//③ScoreService#addScore method is added to the transaction context started at ①   
Participating in existing transaction   
Executing prepared SQL update   
Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]   
SQL update affected 1 rows   
after scoreService.addScore...   
Initiating transaction commit   
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]   
after userService.logon method...  

 

    From the above output log, it is clear that Spring started a new transaction for the UserService#logon() method, while UserServive#updateLastLogonTime() and UserService#logon() are in the same class, no observed Transaction propagation behavior occurs with blocks of code that appear to be "directly merged" into UserService#logon(). 
    However, when the ScoreService#addScore() method is executed, we observe a transaction propagation behavior: "Participating in existing transaction", which means that ScoreService#addScore() is added to the transaction context of UserService#logon(), and the two both share the same transaction. So the end result is that UserService's logon(), updateLastLogonTime(), and ScoreService's addScore all work in the same transaction. 

Guess you like

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