Gather the https://segmentfault.com/a/1190000013341344 preface Spring in the TransactionDefinition interface defines 7 types of transaction propagation behavior. Transaction propagation behavior is a transaction-enhancement feature unique to the Spring framework that does not belong to the actual transaction provider database behavior. This is a powerful toolkit that Spring provides for us, and using transactional propagation rows can provide a lot of convenience for our development efforts. But there are a lot of misconceptions about it, and you’ve heard the myth that it’s best not to nest a service method transaction. To use tools properly, you need to understand them first. This article introduces seven transaction propagation behaviors in detail and presents them as code examples.
Basic concept
- What is transaction propagation behavior? Transaction propagation behavior is used to describe how a transaction propagates when a method modified by one transaction propagation behavior is nested into another method.
In pseudocode:
public void methodA(){ methodB(); //doSomething }
@transaction (Propagation=XXX) public void methodB(){//doSomething} The Propagation behavior of methodB() is determined by @Transaction(Propagation=XXX). It is important to note that methodA() does not start the transaction, and a method that propagates the behavior modification does not have to be called in the outer method that starts the transaction.
- 7 Transaction propagation behavior Type Description PROPAGATION_REQUIRED If there is no transaction, create a new transaction, PROPAGATION_REQUIRED If one already exists, add it to the transaction. This is the most common choice. PROPAGATION_SUPPORTS the current transaction, and if there is no current transaction, it executes in a non-transactional manner. PROPAGATION_MANDATORY uses the current transaction, or throws an exception if there is no transaction currently. PROPAGATION_REQUIRES_NEW Creates a transaction, and suspends the current transaction if one exists. Execute an operation in a non-transactional manner, PROPAGATION_NOT_SUPPORTED, or suspend the current transaction if one exists. PROPAGATION_NEVER executes non-transactionally, and throws an exception if a transaction currently exists. PROPAGATION_NESTED Executes within a nested transaction if a transaction currently exists. If there are no transactions currently, an operation similar to PROPAGATION_REQUIRED is performed. The definition is very simple and easy to understand, so let’s move on to the code test section to verify our understanding.
Code validation The code in the traditional three layers structure of two layers, namely the Service and the Dao layer, is responsible by the Spring dependency injection and annotation type transaction management, Dao layer by Mybatis implementation, you can also use any way like, for example, Hibernate, JPA, JDBCTemplate, etc. The database is a MySQL database, but you can use any database that supports transactions without affecting the validation 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; // The get and set methods omit… } User2
public class User2 { private Integer id; private String name; // The get and set methods omit… } User1Mapper
public interface User1Mapper { int insert(User1 record); User1 selectByPrimaryKey(Integer id); // Other ways to omit… } User2Mapper
public interface User2Mapper { int insert(User2 record); User2 selectByPrimaryKey(Integer id); // Other ways to omit… } Finally, the code for concrete validation is implemented by the Service layer, which is listed in the following cases.
PROPAGATION_REQUIRED We add Propagation.REQUIRED to User1Service and User2Service methods.
User1Service method:
@service public class User1ServiceImpl implements User1Service {// @Override @Transactional(propagation = Propagation.REQUIRED) public void addRequired(User1 user){ user1Mapper.insert(user); }} User2Service method:
@service public class User2ServiceImpl implements User2Service {// @Override @Transactional(propagation = Propagation.REQUIRED) public void addRequired(User2 user){ user2Mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void addRequiredException(User2 user){ user2Mapper.insert(user); throw new RuntimeException(); }
} 1.1 Scenario 1 The peripheral method in this scenario does not enable transactions.
Verification Method 1:
@Override public void notransaction_exception_required_required(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequired(user2); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Override public void notransaction_required_required_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiredException(user2); }Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 Insert Both John and John. The external method does not open the transaction, and the insert “Zhang SAN” and “Li Si” methods run independently in their own transaction. The exception of the external method does not affect the internal insert “Zhang SAN” and “Li Si” method independent transaction. 2 “Zhang SAN” inserts, “Li Si” does not insert. The peripheral method has no transaction, and the insert “Tom” and “Tom” methods run independently in their own transactions, so the insert “Tom” method throws an exception and only rolls back the insert “Tom” method, the insert “Tom” method is not affected. Conclusion: Through these two methods, we proved that the internal methods propagating.REQUIRED will open their own transactions when the external methods do not open transactions, and the opened transactions are independent of each other.
1.2 Scenario 2 Peripheral method to start transactions, this is the scenario with high usage.
Verification Method 1:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_exception_required_required(){ User1 user1=new User1(); User1. Elegantly-named setName (” zhang “); user1Service.addRequired(user1);
User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequired(user2); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_required_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiredException(user2); }Copy the code
Verification Method 3:
@Transactional @Override public void transaction_required_required_exception_try(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); try { user2Service.addRequiredException(user2); } catch (Exception e) {system.out.println (" method rollback "); }}Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 John and John are not inserted. The outer method starts the transaction, the inner method joins the outer method transaction, the outer method rolls back, and the inner method rolls back. 2 “Zhang SAN” and “Li Si” are not inserted. The outer method starts the transaction, the inner method joins the outer method transaction, the inner method throws an exception and rolls back, and the outer method senses the exception and rolls back the whole transaction. 3 “Zhang SAN” and “Li Si” are not inserted. The outer method opens the transaction, the inner method joins the outer method transaction, the inner method throws an exception and rolls back, even if the method is caught and not sensed by the outer method, the whole transaction rolls back. Conclusion: Propagation.REQUIRED and peripheral methods belong to the same transaction. As long as one method is rolled back, the Propagation. The entire transaction is rolled back.
PROPAGATION_REQUIRES_NEW We add the Propagation.REQUIRES_NEW attribute to User1Service and corresponding User2Service methods. User1Service method:
@service public class User1ServiceImpl implements User1Service {// @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addRequiresNew(User1 user){ user1Mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void addRequired(User1 user){ user1Mapper.insert(user); }} User2Service method:
@service public class User2ServiceImpl implements User2Service {// @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addRequiresNew(User2 user){ user2Mapper.insert(user); }
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
Copy the code
} 2.1 Scenario 1 The peripheral method does not open a transaction.
Verification Method 1:
@Override public void notransaction_exception_requiresNew_requiresNew(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequiresNew(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiresNew(user2); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Override public void notransaction_requiresNew_requiresNew_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequiresNew(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiresNewException(user2); }Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 Insert Zhang SAN, Insert Li Si. The outer method does not have a transaction, and the insert “Zhang SAN” and “Li Si” methods run independently in their own transaction. The outer method throws an exception and the rollback does not affect the inner method. The outer method does not start the transaction. The outer method and the outer method respectively start their own transaction. The outer method throws an exception and rolls back, and other transactions are not affected. Conclusion: By using these two methods, we proved that the internal method propagating.REQUIRES_NEW will open its own transaction when the external method does not open the transaction, and the opened transaction is independent of each other.
2.2 Scenario 2 Peripheral Methods Start transactions.
Verification Method 1:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_exception_required_requiresNew_requiresNew(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiresNew(user2); User2 user3=new User2(); User3. Elegantly-named setName (" detective "); user2Service.addRequiresNew(user3); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_requiresNew_requiresNew_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiresNew(user2); User2 user3=new User2(); User3. Elegantly-named setName (" detective "); user2Service.addRequiresNewException(user3); }Copy the code
Verification Method 3:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_requiresNew_requiresNew_exception_try(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addRequired(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addRequiresNew(user2); User2 user3=new User2(); User3. Elegantly-named setName (" detective "); try { user2Service.addRequiresNewException(user3); } catch (Exception e) {system.out.println (" rollback "); }}Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 John is not inserted, Li Is inserted, and Wang is inserted. The external method opens the transaction, inserts “Zhang SAN” method and the external method a transaction, inserts “Li Si” method, inserts “Wang Wu” method respectively in the independent new transaction, the external method throws an exception only rollback and the external method of the same transaction method, so inserts “Zhang SAN” method rollback. 2 “Zhang SAN” is not inserted, “Li Si” is inserted, “Wang Wu” is not inserted. The peripheral method starts the transaction, inserts the “Zhang SAN” method and the peripheral method a transaction, inserts the “Li Si” method, inserts the “Wang wu” method respectively in the independent new transaction. Insert “king” method to throw an exception, first insert “king” method transaction is rolled back, the exception continues to throw is perceived by the peripheral method, the peripheral method transaction is also rolled back, so insert “three” method is also rolled back. 3 “Zhang SAN” inserts, “Li Si” inserts, “Wang Wu” does not insert. The peripheral method starts the transaction, inserts the “Zhang SAN” method and the peripheral method a transaction, inserts the “Li Si” method, inserts the “Wang wu” method respectively in the independent new transaction. Insert the “king five” method to throw an exception, first of all, the transaction inserted into the “king five” method is rolled back, the exception caught will not be perceived by the peripheral method, the peripheral method transaction does not roll back, so insert the “king three” method successfully inserted. Conclusion: The internal methods of Propagation.REQUIRES_NEW still open independent transactions and are independent of external method transactions. Internal methods and external method transactions are independent and do not interfere with each other.
PROPAGATION_NESTED We add Propagation.NESTED to the corresponding methods of User1Service and User2Service. User1Service method:
@service public class User1ServiceImpl implements User1Service {// @Override @Transactional(propagation = Propagation.NESTED) public void addNested(User1 user){ user1Mapper.insert(user); }} User2Service method:
@service public class User2ServiceImpl implements User2Service {// @Override @Transactional(propagation = Propagation.NESTED) public void addNested(User2 user){ user2Mapper.insert(user); }
@Override
@Transactional(propagation = Propagation.NESTED)
public void addNestedException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
Copy the code
} 3.1 Scenario 1 The peripheral method in this scenario does not open a transaction.
Verification Method 1:
@Override public void notransaction_exception_nested_nested(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addNested(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addNested(user2); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Override public void notransaction_nested_nested_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addNested(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addNestedException(user2); }Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 Insert Both John and John. The external method does not open the transaction, and the insert “Zhang SAN” and “Li Si” methods run independently in their own transaction. The exception of the external method does not affect the internal insert “Zhang SAN” and “Li Si” method independent transaction. 2 “Zhang SAN” inserts, “Li Si” does not insert. The peripheral method has no transaction, and the insert “Tom” and “Tom” methods run independently in their own transactions, so the insert “Tom” method throws an exception and only rolls back the insert “Tom” method, the insert “Tom” method is not affected. Results showed that both methods performed the same function when peripheral methods failed to open transactions. Both internal methods open their own transactions independently of each other.
3.2 Scenario 2 Peripheral methods start transactions.
Verification Method 1:
@Transactional @Override public void transaction_exception_nested_nested(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addNested(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addNested(user2); throw new RuntimeException(); }Copy the code
Verification Method 2:
@Transactional @Override public void transaction_nested_nested_exception(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addNested(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); user2Service.addNestedException(user2); }Copy the code
Verification Method 3:
@Transactional @Override public void transaction_nested_nested_exception_try(){ User1 user1=new User1(); User1. Elegantly-named setName (" zhang "); user1Service.addNested(user1); User2 user2=new User2(); User2. Elegantly-named setName (" li si "); try { user2Service.addNestedException(user2); } catch (Exception e) {system.out.println (" method rollback "); }}Copy the code
The verification methods are executed respectively, and the results are as follows:
Verification Method No. Database Result Result Analysis 1 John and John are not inserted. The outer method starts the transaction, the inner transaction is a child of the outer transaction, the outer method rolls back, and the inner method rolls back. 2 “Zhang SAN” and “Li Si” are not inserted. The outer method starts the transaction, the inner transaction is a child of the outer transaction, the inner method throws an exception and rolls back, and the outer method senses the exception and causes the whole transaction to roll back. 3 “Zhang SAN” inserts, “Li Si” does not insert. The outer method opens the transaction, and the inner transaction is a sub-transaction of the outer transaction. Insert the “zhang SAN” inner method throws an exception, which can be rolled back to the sub-transaction separately. Conclusion: To illustrate the Propagation of NESTED internal methods in the case of a peripheral method enabled transaction, a NESTED internal method belongs to the subtransaction of an external transaction. The NESTED internal method can be rolled back independently without affecting the peripheral main transaction and other subtransactions
- By comparing scenarios 1.2 and 3.2, we know that both internal methods modified by NESTED and REQUIRED belong to peripheral method transactions. If the peripheral method throws an exception, the transactions of both methods will be rolled back. But REQUIRED joins the enclosing method transaction, so it belongs to the same transaction as the enclosing method transaction, which will be rolled back once the REQUIRED transaction throws an exception. NESTED is a subtransaction of a peripheral method and has a separate savepoint. Therefore, an exception thrown by a NESTED method is rolled back and does not affect the transactions of the peripheral method.
By comparing “2.2 Scenario 2” and “3.2 Scenario 2”, we can know that both REQUIRES_NEW and internal method transactions can be rolled back without affecting peripheral method transactions. However, because NESTED transactions are NESTED, after the rollback of the peripheral method, the child transactions of the peripheral method transaction are also rolled back. REQUIRES_NEW is implemented by starting a new transaction. The internal and peripheral transactions are two transactions, and the rollback of the peripheral transaction does not affect the internal transaction.
- Other transaction propagation behavior Due to the length of this article, other transaction propagation behavior tests are not described here, interested readers can go to the source code to find the corresponding test code and result interpretation. Portal: https://github.com/TmTse/tran…
Simulation use cases introduce so much transactional propagation behavior, how do we apply it in the real world? Here’s an example:
Suppose we have a registered method that calls the method that adds credits. If we want to add credits without affecting the registration process (i.e., adding credits fails to roll back the registration method), we would write:
@Service public class UserServiceImpl implements UserService {
@Transactional public void register(User user){ try { membershipPointService.addPoint(Point point); } catch (Exception e) {// omit... } // omit... } // omit...Copy the code
} We also specify that if a registration failure affects the addPoint() method (which is rolled back as well as the addPoint() method), the addPoint() method needs to be implemented like this:
@Service public class MembershipPointServiceImpl implements MembershipPointService{
@Transactional(propagation = Propagation.NESTED) public void addPoint(Point point){ try { recordService.addRecord(Record record); } catch (Exception e) {// omit... } // omit... } // omit...Copy the code
} We notice that the addRecord() method is also called in addPoint(), which is used for logging. His implementation is as follows:
@Service public class RecordServiceImpl implements RecordService{
@transactional (propagation = propagation.NOT_SUPPORTED) public void addRecord(propagation){// ellipse... } // omit...Copy the code
}} addRecord() {propagation = propagation.not_supported; So neither the addRecord() method itself nor the peripheral addPoint() method will cause the addRecord() method to roll back, and neither the addRecord() method will affect the execution of the peripheral addPoint() method.
Through this example I believe that you have a more intuitive understanding of the use of transaction propagation behavior, through the combination of various attributes can really make our business implementation more flexible and diverse.