web123456

Spring affairs are explained in detail, full of practical information!

Preface

Spring specifies 7 types of transaction propagation behaviors in the TransactionDefinition interface. Transaction propagation behavior is a transaction enhancement feature unique to the Spring framework, and it does not belong to the database behavior of the transaction actual provider. This is a powerful toolbox that Spring provides us with, and using transaction propagation lines can provide many conveniences for our development efforts. But people have many misunderstandings about it, and you must have heard the rumor that "the service method transactions are best not to be nested." To use tools correctly, you must first understand the tools. This article introduces the seven transaction propagation behaviors in detail, and presents the main code examples of the content.

Basic concepts

1. What is transaction communication behavior?

Transaction propagation behavior is used to describe how transactions are propagated when methods modified by a certain transaction propagation behavior are nested into another method.

Use pseudo-code to explain:

 public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

In codemethodA()Method nested callmethodB()method,methodB()The transaction propagation behavior is@Transaction(Propagation=XXX)Setting decision. What needs to be noted here ismethodA()The transaction is not started, and the method of modifying a transaction propagation behavior does not have to be called in the peripheral method of the transaction that starts.

2. Seven transaction propagation behaviors in Spring

Transaction propagation behavior type illustrate
PROPAGATION_REQUIRED If there is no transaction at present, create a new transaction, and if there is already a transaction, add it to the transaction. This is the most common choice.
PROPAGATION_SUPPORTS Supports the current transaction, and if there is currently no transaction, it will be executed in a non-transactional manner.
PROPAGATION_MANDATORY Use the current transaction and throw an exception if there is currently no transaction.
PROPAGATION_REQUIRES_NEW Create a new transaction. If the transaction currently exists, suspend the current transaction.
PROPAGATION_NOT_SUPPORTED Execute operations in a non-transactional manner, and if a transaction currently exists, the current transaction is suspended.
PROPAGATION_NEVER Executes in a non-transactional manner, throws an exception if a transaction is currently present.
PROPAGATION_NESTED If a transaction currently exists, it is executed within a nested transaction. If there is currently no transaction, perform an operation similar to PROPAGATION_REQUIRED.

The definition is very simple and easy to understand. Let’s go to the code test section to verify whether our understanding is correct.

Code Verification

The code in this article is presented in two layers in a traditional three-layer structure, namely the Service and Dao layer. Spring is responsible for dependency injection and annotation transaction management. The DAO layer is implemented by Mybatis. You can also use any favorite method, such as Hibernate, JPA, JDBCTemplate, etc. The database uses MySQL database, and you can also use any transaction-enabled database, which will not affect the verification results.

First we create two tables in the database:

user1

CREATE TABLE `user1` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;

user2

CREATE TABLE `user2` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;

Then write the corresponding Bean and DAO layer code:

User1

public class User1 {
     private Integer id;
     private String name;
    //Get and set methods are omitted...
 }

User2

public class User2 {
     private Integer id;
     private String name;
    //Get and set methods are omitted...
 }

User1Mapper

public interface User1Mapper {
     int insert(User1 record);
     User1 selectByPrimaryKey(Integer id);
     //Other methods are omitted...
 }

User2Mapper

public interface User2Mapper {
     int insert(User2 record);
     User2 selectByPrimaryKey(Integer id);
     //Other methods are omitted...
 }

Finally, the specific verification code is implemented by the service layer, and we will list it in the following situations.

1.PROPAGATION_REQUIRED

We add the corresponding methods of User1Service and User2Serviceproperty.

User1Service method:

@Service
 public class User1ServiceImpl implements User1Service {
     //Omit other...
     @Override
     @Transactional(propagation = )
     public void addRequired(User1 user){
         (user);
     }
 }

User2Service method:

@Service
 public class User2ServiceImpl implements User2Service {
     //Omit other...
     @Override
     @Transactional(propagation = )
     public void addRequired(User2 user){
         (user);
     }
     @Override
     @Transactional(propagation = )
     public void addRequiredException(User2 user){
         (user);
         throw new RuntimeException();
     }
    
 }

1.1 Scene 1

This scenario peripheral method does not enable transactions.

Verification method 1:

@Override
     public void nottransaction_exception_required_required(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
        
         throw new RuntimeException();
     }

Verification method 2:

@Override
     public void nottransaction_required_required_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" and "Li Si" are both inserted. The peripheral method has not started the transaction, and the insertion of "Zhang San" and "Li Si" methods run independently in their own transactions. The abnormal peripheral method does not affect the internal insertion of "Zhang San" and "Li Si" methods.
2 "Zhang San" is inserted, but "Li Si" is not inserted. The peripheral method has no transactions, and the methods of inserting "Zhang San" and "Li Si" are both run independently in their own transactions, so inserting "Li Si" method will only roll back the "Li Si" method, and inserting "Zhang San" method will not be affected.

Conclusion: Through these two methods, we prove that if the peripheral method does not open the transactionThe modified internal method will start its own transactions, and the transactions opened will be independent of each other and do not interfere with each other.

1.2 Scene 2

The peripheral method starts the transaction, which is a scenario with a relatively high usage rate.

Verification method 1:

@Override
     @Transactional(propagation = )
     public void transaction_exception_required_required(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
        
         throw new RuntimeException();
     }

Verification method 2:

@Override
     @Transactional(propagation = )
     public void transaction_required_required_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
     }

Verification method 3:

@Transactional
     @Override
     public void transaction_required_required_exception_try(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         try {
             (user2);
         } catch (Exception e) {
             ("Method Rollback");
         }
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" and "Li Si" were not inserted. The peripheral method starts the transaction, the internal method joins the peripheral method transaction, the peripheral method rolls back, and the internal method also needs to be rolled back.
2 "Zhang San" and "Li Si" were not inserted. The peripheral method opens the transaction, the internal method adds the peripheral method transaction, the internal method throws an exception rollback, and the peripheral method perceives the exception causing the overall transaction to rollback.
3 "Zhang San" and "Li Si" were not inserted. The peripheral method starts the transaction, the internal method joins the peripheral method transaction, and the internal method throws an exception rollback. Even if the method is caught and is not perceived by the peripheral method, the entire transaction is still rolled back.

Conclusion: The above test results show that when the peripheral method opens the transactionThe modified internal methods will be added to the transaction of the peripheral methods, allThe modified internal methods and peripheral methods are both the same transaction. As long as one method rolls back, the entire transaction will be rolled back.

2.PROPAGATION_REQUIRES_NEW

We add the corresponding methods of User1Service and User2ServicePropagation.REQUIRES_NEWproperty.
User1Service method:

@Service
 public class User1ServiceImpl implements User1Service {
     //Omit other...
     @Override
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void addRequiresNew(User1 user){
         (user);
     }
     @Override
     @Transactional(propagation = )
     public void addRequired(User1 user){
         (user);
     }
 }

User2Service method:

@Service
 public class User2ServiceImpl implements User2Service {
     //Omit other...
     @Override
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void addRequiresNew(User2 user){
         (user);
     }
    
     @Override
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void addRequiresNewException(User2 user){
         (user);
         throw new RuntimeException();
     }
 }

2.1 Scene 1

The peripheral method does not enable transactions.

Verification method 1:

@Override
     public void nottransaction_exception_requiresNew_requiresNew(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
         throw new RuntimeException();
        
     }

Verification method 2:

@Override
     public void nottransaction_requiresNew_requiresNew_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" is inserted, and "Li Si" is inserted. The peripheral method has no transactions. The insertion of "Zhang San" and "Li Si" methods are run independently in their own transactions. The exception rollback of the peripheral method will not affect the internal method.
2 "Zhang San" is inserted, "Li Si" is not inserted The peripheral method does not start the transaction. The insertion of the "Zhang San" method and the insertion of the "Li Si" method start their own transactions respectively. The insertion of the "Li Si" method throws an exception rollback, and other transactions are not affected.

Conclusion: Through these two methods, we prove that if the peripheral method does not open the transactionPropagation.REQUIRES_NEWThe modified internal method will start its own transactions, and the transactions opened will be independent of each other and do not interfere with each other.

2.2 Scene 2

Peripheral method starts the transaction.

Verification method 1:

@Override
     @Transactional(propagation = )
     public void transaction_exception_required_requiresNew_requiresNew(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
        
         User2 user3=new User2();
         ("Wang Wu");
         (user3);
         throw new RuntimeException();
     }

Verification method 2:

@Override
     @Transactional(propagation = )
     public void transaction_required_requiresNew_requiresNew_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
        
         User2 user3=new User2();
         ("Wang Wu");
         (user3);
     }

Verification method 3:

@Override
     @Transactional(propagation = )
     public void transaction_required_requiresNew_requiresNew_exception_try(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
         User2 user3=new User2();
         ("Wang Wu");
         try {
             (user3);
         } catch (Exception e) {
             ("rollback");
         }
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" was not inserted, "Li Si" was inserted, and "Wang Wu" was inserted. The peripheral method starts the transaction, inserts a transaction of the "Zhang San" method and the peripheral method, inserts the "Li Si" method and the "Wang Wu" method respectively. The peripheral method throws an exception and only rolls back the same transaction as the peripheral method, so the method of inserting the "Zhang San" method rolls back.
2 "Zhang San" was not inserted, "Li Si" was inserted, and "Wang Wu" was not inserted. The peripheral method starts the transaction, inserts a transaction of the "Zhang San" method and the peripheral method, inserts the "Li Si" method and the "Wang Wu" method in independent new transactions. When the "Wang Wu" method is inserted, the transaction that is inserted into the "Wang Wu" method is rolled back. The exception continues to be thrown and perceived by the peripheral method. The transaction of the peripheral method is also rolled back, so the "Zhang San" method is also rolled back.
3 "Zhang San" is inserted, "Li Si" is inserted, and "Wang Wu" is not inserted. The peripheral method starts the transaction, inserts a transaction of the "Zhang San" method and the peripheral method, inserts the "Li Si" method and the "Wang Wu" method in independent new transactions. The "Wang Wu" method is inserted and the transaction that inserts the "Wang Wu" method is rolled back first. The exception is caught and will not be perceived by the peripheral method. The transaction of the peripheral method is not rolled back, so the insertion of the "Zhang San" method is successfully inserted.

Conclusion: In the case of peripheral methods opening transactionsPropagation.REQUIRES_NEWThe modified internal method will still open independent transactions separately and are also independent of external method transactions. The internal method, internal method and external method transactions are independent of each other and do not interfere with each other.

3.PROPAGATION_NESTED

We add the corresponding methods of User1Service and User2Serviceproperty.
User1Service method:

@Service
 public class User1ServiceImpl implements User1Service {
     //Omit other...
     @Override
     @Transactional(propagation = )
     public void addNested(User1 user){
         (user);
     }
 }

User2Service method:

@Service
 public class User2ServiceImpl implements User2Service {
     //Omit other...
     @Override
     @Transactional(propagation = )
     public void addNested(User2 user){
         (user);
     }
    
     @Override
     @Transactional(propagation = )
     public void addNestedException(User2 user){
         (user);
         throw new RuntimeException();
     }
 }

3.1 Scene 1

This scenario peripheral method does not enable transactions.

Verification method 1:

@Override
     public void nottransaction_exception_nested_nested(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
         throw new RuntimeException();
     }

Verification method 2:

@Override
     public void nottransaction_nested_nested_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" and "Li Si" are both inserted. The peripheral method has not started the transaction, and the insertion of "Zhang San" and "Li Si" methods run independently in their own transactions. The abnormal peripheral method does not affect the internal insertion of "Zhang San" and "Li Si" methods.
2 "Zhang San" is inserted, but "Li Si" is not inserted. The peripheral method has no transactions, and the methods of inserting "Zhang San" and "Li Si" are both run independently in their own transactions, so inserting "Li Si" method will only roll back the "Li Si" method, and inserting "Zhang San" method will not be affected.

Conclusion: Through these two methods, we prove that if the peripheral method does not open the transactionandThe functions of the same, the modified internal methods will start their own transactions, and the transactions that are opened are independent of each other and do not interfere with each other.

3.2 Scene 2

Peripheral method starts the transaction.

Verification method 1:

@Transactional
     @Override
     public void transaction_exception_nested_nested(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
         throw new RuntimeException();
     }

Verification method 2:

@Transactional
     @Override
     public void transaction_nested_nested_exception(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         (user2);
     }

Verification method 3:

@Transactional
     @Override
     public void transaction_nested_nested_exception_try(){
         User1 user1=new User1();
         ("Zhang San");
         (user1);
        
         User2 user2=new User2();
         ("Li Si");
         try {
             (user2);
         } catch (Exception e) {
             ("Method Rollback");
         }
     }

Execute verification methods separately, and the results:

Verification method serial number Database results Results Analysis
1 "Zhang San" and "Li Si" were not inserted. The peripheral method starts the transaction, and the internal transaction is a sub-transaction of the peripheral transaction. The peripheral method rolls back, and the internal method also needs to be rolled back.
2 "Zhang San" and "Li Si" were not inserted. The peripheral method starts the transaction, and the internal transaction is a sub-transaction of the peripheral transaction. The internal method throws an exception rollback, and the peripheral method perceives the exception causing the overall transaction to rollback.
3 "Zhang San" is inserted, and "Li Si" is not inserted. The peripheral method starts the transaction, and the internal transaction is a sub-transaction of the peripheral transaction. Insert the "Zhang San" internal method to throw an exception, and the child transaction can be rolled back separately.

Conclusion: The above test results show that when the peripheral method opens the transactionThe modified internal method belongs to the sub-transaction of the external transaction. The peripheral main transaction is rolled back, and the sub-transaction must be rolled back, while the internal sub-transaction can be rolled back separately without affecting the peripheral main transaction and other sub-transactions.

4. Similarities and similarities of REQUIRED, REQUIRES_NEW, NESTED

From the comparison of "1.2 Scene 2" and "3.2 Scene 2", we can see:
The internal methods modified by NESTED and REQUIRED are both peripheral method transactions. If the peripheral method throws an exception, the transactions of both methods will be rolled back. However, REQUIRED joins peripheral method transactions, so it belongs to the same transaction as peripheral transactions. Once the REQUIRED transaction throws an exception and is rolled back, peripheral method transactions will also be rolled back. NESTED is a sub-transaction of the peripheral method and has a separate save point, so the NESTED method throws an exception and is rolled back, which will not affect the transaction of the peripheral method.

From the comparison of "2.2 Scene 2" and "3.2 Scene 2", we can see:
Both NESTED and REQUIRES_NEW can roll back internal method transactions without affecting peripheral method transactions. However, because NESTED is a nested transaction, after the peripheral method is rolled back, the sub-transactions that are peripheral method transactions will also be rolled back. REQUIRES_NEW is implemented by opening a new transaction. Internal transactions and peripheral transactions are two transactions. Peripheral transaction rollback will not affect internal transactions.

5. Other transaction propagation behaviors

In view of the article's length issue, the tests of other transaction propagation behaviors will not be described here. Interested readers can search for the corresponding test code and result explanations in the source code. Portal:/TmTse/tran...

Simulation Use Cases

After introducing so many transaction communication behaviors, how do we apply them in our actual work? Let me give you an example:

Suppose we have a registered method, in which the method of adding points is called. If we want to add points to not affect the registration process (that is, the rollback failed to add points cannot make the registration method rollback also), we will write this:

@Service
    public class UserServiceImpl implements UserService {
        
         @Transactional
         public void register(User user){
                   
             try {
                 (Point point);
             } catch (Exception e) {
                //Omitted...
             }
             //Omitted...
         }
         //Omitted...
    }

We also stipulate that registration failure will affectaddPoint()Method (Register method rollback also requires rollback)addPoint()The method needs to be implemented like this:

@Service
    public class MembershipPointServiceImpl implements MembershipPointService{
        
         @Transactional(propagation = )
         public void addPoint(Point point){
                   
             try {
                 (Record record);
             } catch (Exception e) {
                //Omitted...
             }
             //Omitted...
         }
         //Omitted...
    }

We noticedaddPoint()Also calledaddRecord()Method, this method is used to record logs. His implementation is as follows:

@Service
    public class RecordServiceImpl implements RecordService{
        
         @Transactional(propagation = Propagation.NOT_SUPPORTED)
         public void addRecord(Record record){
                   
           
             //Omitted...
         }
         //Omitted...
    }

We've noticedaddRecord()In the methodpropagation = Propagation.NOT_SUPPORTED, because there is no need to be accurate for logs, you can have more or less one, soaddRecord()Method itself and peripheraladdPoint()The method will not cause any exception to throwaddRecord()method rollback, andaddRecord()The method throws an exception and will not affect the peripheraladdPoint()Execution of the method.

Through this example, I believe that everyone has a more intuitive understanding of the use of transaction communication behavior. The combination of various attributes can indeed make our business implementation more flexible and diverse.

Source of reprint:/a/1190000013341344, help others promote it.