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 business is 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 } methodA() method in the code calls methodB() method in nested, and the transaction propagation behavior of methodB() is determined by @Transaction(Propagation=XXX) setting. It should be noted here that methodA() does not start the transaction, and the method of modifying a certain transaction propagation behavior does not have to be called in the peripheral method of starting the transaction.
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, and throws an exception if a transaction currently exists. |
| 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 Propagation.REQUIRED attributes to the corresponding methods of User1Service and User2Service.
User1Service method:
@Servicepublic class User1ServiceImpl implements User1Service { //Omit other... @Override @Transactional(propagation = Propagation.REQUIRED) public void addRequired(User1 user){ user1Mapper.insert(user); }}User2Service method:
@Servicepublic class User2ServiceImpl implements User2Service { //Omit other... @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 Scene 1
This scenario peripheral method does not enable transactions.
Verification method 1:
@Override public void nottransaction_exception_required_required(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequired(user2); throw new RuntimeException(); }Verification method 2:
@Override public void nottransaction_required_required_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiredException(user2); }Execute verification methods separately, and the results:
Analysis of results of verification method serial number database
| 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 the internal method modified by Propagation.REQUIRED will newly open its own transactions when the peripheral method does not open the transaction, and the opened transactions are 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 = Propagation.REQUIRED) public void transaction_exception_required_required(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequired(user2); throw new RuntimeException(); }Verification method 2:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_required_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiredException(user2); }Verification method 3:
@Transactional @Override public void transaction_required_required_exception_try(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); try { user2Service.addRequiredException(user2); } catch (Exception e) { System.out.println("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 opens 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 experimental results show that when the peripheral method opens the transaction, the internal methods modified by Propagation.REQUIRED will be added to the transaction of the peripheral method. All internal methods and peripheral methods modified by Propagation.REQUIRED belong to the same transaction. As long as one method rolls back, the entire transaction will be rolled back.
2.PROPAGATION_REQUIRES_NEW
We add the Propagation.REQUIRES_NEW attribute to the corresponding methods of User1Service and User2Service.
User1Service method:
@Servicepublic class User1ServiceImpl implements User1Service { //Omit other... @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:
@Servicepublic class User2ServiceImpl implements User2Service { //Omit other... @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(); }} 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(); user1.setName("Zhang San"); user1Service.addRequiresNew(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiresNew(user2); throw new RuntimeException(); }Verification method 2:
@Override public void nottransaction_requiresNew_requiresNew_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequiresNew(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiresNewException(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 the internal method modified by Propagation.REQUIRES_NEW will newly start its own transactions, and the opened transactions are 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 = Propagation.REQUIRED) public void transaction_exception_required_requiresNew_requiresNew(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiresNew(user2); User2 user3=new User2(); user3.setName("Wang Wu"); user2Service.addRequiresNew(user3); throw new RuntimeException(); }Verification method 2:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_requiresNew_requiresNew_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiresNew(user2); User2 user3=new User2(); user3.setName("Wang Wu"); user2Service.addRequiresNewException(user3); }Verification method 3:
@Override @Transactional(propagation = Propagation.REQUIRED) public void transaction_required_requiresNew_requiresNew_exception_try(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addRequired(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addRequiresNew(user2); User2 user3=new User2(); user3.setName("Wang Wu"); try { user2Service.addRequiresNewException(user3); } catch (Exception e) { System.out.println("Rollingback"); } }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 in the independent newly created transaction. 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 is 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. 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: When the peripheral method opens the transaction, the internal method modified by Propagation.REQUIRES_NEW will still open independent transactions separately, and it is also independent of the 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 Propagation.NESTED attributes to the corresponding methods of User1Service and User2Service.
User1Service method:
@Servicepublic class User1ServiceImpl implements User1Service { //Omit other... @Override @Transactional(propagation = Propagation.NESTED) public void addNested(User1 user){ user1Mapper.insert(user); }}User2Service method:
@Servicepublic class User2ServiceImpl implements User2Service { //Omit other... @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(); }} 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(); user1.setName("Zhang San"); user1Service.addNested(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addNested(user2); throw new RuntimeException(); }Verification method 2:
@Override public void nottransaction_nested_nested_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addNested(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addNestedException(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 Propagation.NESTED and Propagation.REQUIRED have the same functions when the peripheral method does not open the transaction. The modified internal methods will start their own transactions again, and the opened transactions 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(); user1.setName("Zhang San"); user1Service.addNested(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addNested(user2); throw new RuntimeException(); }Verification method 2:
@Transactional @Override public void transaction_nested_nested_exception(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addNested(user1); User2 user2=new User2(); user2.setName("Li Si"); user2Service.addNestedException(user2); }Verification method 3:
@Transactional @Override public void transaction_nested_nested_exception_try(){ User1 user1=new User1(); user1.setName("Zhang San"); user1Service.addNested(user1); User2 user2=new User2(); user2.setName("Li Si"); try { user2Service.addNestedException(user2); } catch (Exception e) { System.out.println("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 transaction, the internal method modified by Propagation.NESTED belongs to the sub-transaction of the external transaction. The peripheral main transaction rolls back, and the sub-transaction must roll back. 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: https://github.com/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 { membershipPointService.addPoint(Point point); } catch (Exception e) { //Omit... } //Omit... } //Omit... } We also stipulate that registration failure will affect addPoint() method (the registration method rollback also requires rollback), so 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... } We noticed that addRecord() addPoint() , which 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){ //Omit... } //Omit... } We noticed propagation = Propagation.NOT_SUPPORTED in addRecord() method, because it is not accurate to the log, and one can be more or less, so addRecord() method itself and the peripheral addPoint() method will not cause addRecord() method to roll back, and addRecord() method to throw an exception will not affect the execution of the peripheral addPoint() 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.
in conclusion
Through the above introduction, I believe that everyone has a deeper understanding of Spring transaction communication behavior, and I hope that your daily development work will be helpful.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.