Due to the details of the content is too much, so only part of the knowledge point screenshots out of the rough introduction, each small node there are more detailed content!
Sorted out a Java core knowledge points. It covers JVM, locking, concurrency, Java reflection, Spring principle, microservices, Zookeeper, database, data structure and many other knowledge points.
If you want to get this document, you can scan below
Here we go
preface
Hello everyone, I am Nezha! I found this excellent article while looking at the Spring transaction propagation behavior section while reorganizing the Spring transaction related content.
Spring specifies seven types of transaction propagation behavior in the TransactionDefinition interface. 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
1. 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
}Copy the code
Propagation of methodB() is determined by @Transaction(Propagation=XXX). 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.
2. Seven transaction propagation behaviors in Spring
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 this article is presented in two of the traditional three-tier structures, namely the Service and Dao layers, with Spring responsible for dependency injection and annotated transaction management, and the Dao layer implemented by Mybatis. You can also use any method you 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;Copy the code
user2
CREATE TABLE `user2` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT ' ',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;Copy the code
Then write the corresponding Bean and DAO layer code:
User1
public class User1 { private Integer id; private String name; / / get andsetOmit... }Copy the code
User2
public class User2 { private Integer id; private String name; / / get andsetOmit... }Copy the code
User1Mapper
public interface User1Mapper { int insert(User1 record); User1 selectByPrimaryKey(Integer id); // Other ways to omit... }Copy the code
User2Mapper
public interface User2Mapper { int insert(User2 record); User2 selectByPrimaryKey(Integer id); // Other ways to omit... }Copy the code
Finally, the verification code is implemented by the Service layer, which is listed in the following cases.
1.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); }}Copy the code
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(); }}Copy the code
1.1 a scene
The scenario peripheral method does not open a transaction.
Verification Method 1:
@Override
public void notransaction_exception_required_required(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequired(user2);
throw new RuntimeException();
}Copy the code
Verification Method 2:
@Override
public void notransaction_required_required_exception(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiredException(user2);
}Copy the code
The verification methods are executed respectively, and the results are as follows:
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 methods open transactions, which is a highly used scenario.
Verification Method 1:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
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.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiredException(user2);
}Copy the code
Verification Method 3:
@Transactional
@Override
public void transaction_required_required_exception_try(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
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:
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.
2.PROPAGATION_REQUIRES_NEW
We add the Propagation.REQUIRES_NEW attribute to the corresponding methods of User1Service and User2Service. 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); }}Copy the code
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 a scene
The peripheral method did not start the transaction.
Verification Method 1:
@Override
public void notransaction_exception_requiresNew_requiresNew(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiresNew(user2);
throw new RuntimeException();
}Copy the code
Verification Method 2:
@Override
public void notransaction_requiresNew_requiresNew_exception(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addRequiresNew(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiresNewException(user2);
}Copy the code
The verification methods are executed respectively, and the results are as follows:
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
The 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");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("Fifty");
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.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("Fifty");
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.setName("Zhang");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("Fifty");
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:
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.
3.PROPAGATION_NESTED
We add the Propagation.NESTED attribute 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); }}Copy the code
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 a scene
The scenario peripheral method does not open a transaction.
Verification Method 1:
@Override
public void notransaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addNested(user2);
throw new RuntimeException();
}Copy the code
Verification Method 2:
@Override
public void notransaction_nested_nested_exception(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addNestedException(user2);
}Copy the code
The verification methods are executed respectively, and the results are as follows:
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
The peripheral method starts the transaction.
Verification Method 1:
@Transactional
@Override
public void transaction_exception_nested_nested(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("Bill");
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.setName("Zhang");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("Bill");
user2Service.addNestedException(user2);
}Copy the code
Verification Method 3:
@Transactional
@Override
public void transaction_nested_nested_exception_try(){
User1 user1=new User1();
user1.setName("Zhang");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("Bill");
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:
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
4. The REQUIRED the REQUIRES_NEW, NESTED similarities and differences
By comparing “scenario 2” and “Scenario 2”, we know that the inner methods modified by NESTED and REQUIRED are 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.
5. Other transaction communication behaviors
Due to the length of this article, other tests of transaction propagation behavior 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/transaction-test
Simulation case
With all this talk about transaction propagation behavior, how do we apply it in practice? 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), then 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(Record Record){ } // omit... }Copy the code
In the addRecord() method “propagation = propagation.not_supported”, because there is no precision in logging, you can have one more or one less. 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.
conclusion
Through the above introduction, I believe you have a deeper understanding of Spring transaction propagation behavior, I hope you can help with daily development work.
Due to the details of the content is too much, so only part of the knowledge point screenshots out of the rough introduction, each small node there are more detailed content!
Sorted out a Java core knowledge points. It covers JVM, locking, concurrency, Java reflection, Spring principle, microservices, Zookeeper, database, data structure and many other knowledge points.
If you want to get this document, you can scan below