[understanding of Spring transaction propagation mechanism]

1. What is a business?

Database transaction is simply to operate several different SQL statements as a whole, either all successful or all failed. It has four characteristics: atomicity, consistency, isolation and persistence.
Spring transaction is a transaction processing mechanism encapsulated in database transaction. It has two management methods: programming transaction and declarative transaction. In normal use, we mostly use declarative transaction management, which is also the way recommended by spring.

In today's work, I encountered the propagation mechanism of transactions. I haven't been able to understand this aspect before. Here we are looking for deficiencies and making up for them. Let's learn more.

2. Communication mechanism of affairs

The transaction propagation mechanisms in Spring include:

  • REQUIRED
  • REQUIRED_NEW
  • NESTED
  • SUPPORTS
  • NOT_SUPPORTED
  • MANDATORY
  • NEVER

Many online introductions are relatively written, which also leads to their initial contact with this area, which can not be well understood. I met this convenient problem in business today, and then I felt a little understanding when I looked back at these things. Next, I will talk about my understanding of the transaction propagation mechanism according to my own words.

Transaction propagation: what is transaction propagation? In my previous study, I didn't understand that affairs can spread. You can combine the problems I encountered today to see: Recently, I am working on the company's warehouse management system, which has such a function. First, the goods need to be warehoused. After the goods are warehoused, the inventory is divided into two parts. One is the inventory of the warehouse itself (how much inventory is there in the whole warehouse), and the other is the inventory for users (users rent the company's warehouse, and users themselves also need to maintain their own inventory changes). In this case, when a commodity is warehoused, what I need to do is to add the commodity to both the inventory of the whole warehouse and the user's inventory. These are two different operations, but there is a connection between them. The total inventory of all users is equal to the total inventory of the warehouse.

Here is a simple code logic:

//Goods warehousing 
@Transactional(rollbackFor = Exception.class)
public ResponseResult productStockIn(){
//1. Goods are warehoused and added to the partition of the corresponding warehouse.
wareHouseStockIn();
//2. The user's inventory will change according to which user the goods in stock belong to
userStockIn();
}
//Add inventory to warehouse
@Transactional(rollbackFor = Exception.class)
public Boolean wareHouseStockIn(){
	//1. Location inventory modification
	
	//2. Modify inventory by zone
	
	//3. Warehouse inventory modification
}
//User inventory addition
@Transactional(rollbackFor = Exception.class)
public Boolean userStockIn(){
 //User inventory modification
 //Inventory operation log add	
}

We can see that in the above code logic, first of all, two steps are required in the method of commodity warehousing. In principle, these two steps are an atomic operation, which can only succeed or fail at the same time, otherwise there will be exceptions in the inventory of the whole warehouse management. Therefore, you need to add a practical operation.

In the step of adding inventory to the warehouse, the warehouse is divided into different partitions, and each partition is divided into different locations. The real storage location of goods is in the warehouse. However, the three different levels of location, partition and warehouse need to maintain their own inventory. Therefore, the three-step operation of adding inventory in the warehouse is also an atomic operation in principle, so transactions also need to be added.

In the step of adding inventory, the two steps of adding inventory and adding inventory operation record are also atomic operations in principle. You also need to add transactions.
The above code also needs to add locks to ensure the concurrency safety of the program. The functions of this part will be considered later and will not be mentioned today.

Another possibility is to put all the above code in one method and add a transaction on top of this method. This operation does not exist the propagation of transactions we will talk about next, but the readability and flexibility of the code written by this method are relatively poor. It is also very troublesome to maintain the rear, which is not a wise move.

However, there is a problem with the method of splitting into one by one. There is already a transaction in the method of commodity warehousing, there is also a transaction in the step of adding inventory in the warehouse, and there is also a transaction in the step of adding inventory by users. These are three different transactions. When the final program is executed, when does it start a transaction and when does it commit a transaction? If this point is not understood, there is no way to guarantee the atomicity of affairs.

For example, in the first part, a transaction is started in the step of commodity warehousing, and in the second step of warehouse inventory modification, a transaction is also started. At this time, a new transaction is started in the transaction? Or didn't you start a transaction? If a transaction is started, is the first transaction committed? These are problems. These problems that may occur in two different transactions are solved by the transaction propagation mechanism.

Therefore, it can be simply understood as: the transaction propagation mechanism is to solve how to ensure the atomicity of the transaction itself when there are transactions in two different methods, or when there is a transaction and there is no transaction.

The seven transaction propagation mechanisms mentioned at the beginning of the article correspond to different states.

Let's talk about it in detail:

3. Explain the communication mechanism of affairs in detail

Here we learn from an example on the Internet. I think it's very good.

3.1 REQUIRED

REQUIRED means necessary, regulated and ideal. This is also the default transaction propagation mechanism of Spring.

The written explanation is that if there is no transaction in the current method, a new transaction will be created. If there is a transaction in the current method, it will be added to the transaction.

When I first saw this sentence, I was very confused. Anyway, I couldn't understand it. This is also a common problem of written language and a high degree of generalization. A simple way to understand is that there are two ways to do A and B, and to invoke the B method in the A method. At this time, if there is no transaction in method a and there is a transaction in method B, then when running to method B, method B will start a new transaction by itself, and the transaction will be committed when the operation of method B ends. If there are transactions in method a and method B, then method B does not need to start a new transaction, but join the transaction of method a. At this time, the transaction is started in method A. after the operation of method B and method a ends, only the transaction of method a works again in the whole process of submitting the transaction, but the transaction of method B does not work.

  1. The caller has a transaction
@Service
public class BookServiceImpl implements BookService {

    private final BookMapper bookMapper;
    private final TitleService titleService;

    @Override
    @Transactional
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transaction in Caller:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //Communication services
        titleService.titleTransaction();
    }
}
public class TitleServiceImpl implements TitleService {

    private final TitleMapper titleMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transactions in callee:{}", transactionName);
        titleMapper.insert(new Title().setName("Chapter I"));
    }
}
// Output, the callee uses the caller's transaction
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
 Transactions in callee: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest

You can see that the transactions in the two methods are the same, and both are the transactions of the first method.

  1. Caller has no transaction
public class BookServiceImpl implements BookService {

    private final BookMapper bookMapper;
    private final TitleService titleService;

    @Override
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transaction in Caller:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //Communication services
        titleService.titleTransaction();
    }
}
public class TitleServiceImpl implements TitleService {

    private final TitleMapper titleMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transactions in callee:{}", transactionName);
        titleMapper.insert(new Title().setName("Chapter I"));
    }
}
// Output, the callee creates a new transaction
 Transaction in Caller: null
 Transactions in callee: com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest

The first method has no transaction, and the second method opens a new transaction itself.

3.2 REQUIRES_NEW

REQUIRES_ REQUIRED in NEW means REQUIRED. NEW means NEW.

The above meaning can be simply understood as that it must be a new one.

That is, the propagation mechanism of transactions is requirements_ New time, we must innovate A new business. For example: call method B in method A. If there are transactions in method A, there are transactions in method B. When the code in method A is executed to method B, A new transaction will be started for method B, and the transaction of the original method A is suspended (suspended means that the transaction of method A is put aside, I (method B) do not use the transaction of method A, and I create my own transaction.) The transaction of method A is irrelevant to the transaction of method B.

When an exception needs to be rolled back, the rollback of the internal transaction method does not affect the peripheral transaction method. The rollback of peripheral transaction methods will not affect internal transactions. They are completely independent and have independent isolation.

The code is as follows:

  1. The caller has a transaction
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //Communication services
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
}
// Output, callee and caller are two different transactions
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
 Transactions in callee: com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest
  1. Caller has no transaction

In this case, method B will certainly create a new transaction, which is the same as when the caller in REQUIRED has no transaction. I won't repeat it here.

However, the propagation mechanism of the transaction is required_ In new, there will be a special situation that needs attention.

Phenomenon description:
I When ServiceA A () method calls serviceb When using the B () method, the inner transaction commit and rollback are not affected by the outer transaction commit or rollback.
II When ServiceA A () method calls ServiceA B () method, the inner transaction cannot be committed or rolled back correctly. (that is, the commit and rollback of inner transactions are affected by outer transactions, and they are no longer independent.)

Analysis and conclusion:

@Service
public class ReadService{

	@Transactional(rollbackFor = Exception.class)
	public void methodA(){
		methodB();
	}

	@Transactional(rollbackFor = Exception.class,propagation=Propagation.REQUIRES_NEW)
	public void methodB(){
		//do something
	}
}

In the same class, when the method B is called in the method A, the transaction propagation mechanism of method B is REQUIRED_ at this time. New, but in this case, method B will not create a new transaction, and the two methods use the same transaction. Therefore, the inner affairs will be affected by the outer affairs.
However, if method A and method B are in different classes, then method A calls method B, and the transaction propagation mechanism is required_ The role of new will be reflected.
So why is the transaction propagation mechanism in Spring required_ Does new only work for methods in different classes? When the Debug code finds a method called across services, it will go through org springframework. aop. framework. CglibAopProxy. DynamicAdvisedInterceptor. The intercept () method can only control the transaction through here.

3.3 NESTED

NESTED means NESTED. If the NESTED meaning is brought into the transaction, it means that there are transactions in both methods. The transaction of the callee method is NESTED in the caller's transaction. In this case, the caller's transaction is the parent transaction and the callee's transaction is the child transaction. Although they are two different transactions, they are closely related.

The B method is invoked in the A method (existing transaction), and the transactional propagation mechanism of the B method is NESTED. When method a executes code, a sub transaction will be created when method B executes.
The difference between two transaction commits: after the parent transaction commits, the child transaction commits again.
The difference between the exception handling of the two transactions: the child transaction is abnormal, and the parent transaction is not necessarily rolled back. However, if the parent transaction is abnormal, the child transaction must be rolled back.

  1. Sub transaction exception
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    try {
        // Propagate transactions and catch exceptions
        titleService.titleTransaction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
    throw new RuntimeException("Sub transaction exception");
}
// Output: the title table insertion is rolled back, but the book table insertion is not rolled back
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
 Transactions in callee: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
java.lang.RuntimeException: Sub transaction exception
  1. Parent transaction exception
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //Communication services
    titleService.titleTransaction();
    throw new RuntimeException("Parent transaction exception");
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
}
// Output, book insertion and title insertion are rolled back
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
 Transactions in callee: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
java.lang.RuntimeException: Parent transaction exception

3.4 SUPPORTS

SUPPORTS means to support and endure.

The specific meaning is to join the transaction if there is a transaction in the current method. If the current method does not have a transaction, it runs in a non transactional manner.
The meaning of this sentence is a little difficult to understand. You can refer to the specific code to understand.

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
   methodB();
   // do something
}
// The transaction attribute is SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}
public void methodC(){
	methodB();
   // do something
}

When simply calling methodB, the methodB method is executed according to non transaction.
When methodB is invoked in methodC, the methodB method is executed according to non transactions.
When methodB is invoked in methodA, methodB joins methodA transactions, which are somewhat similar to the propagation mechanism of transaction and REQUIRED.

3.5 NOT_SUPPORTED

NOT_SUPPORTED is easy to understand according to the literal meaning.

Transactions are not supported. Transactions are also executed in a non transactional manner.

This kind of communication behavior is that it can't be transmitted to me anyway. I just carry out it in a non transactional way all the time.

@Override
@Transactional(rollbackFor=Exception.class)
public void A(Student student){
	String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
	//Method A: execute the add operation
	student.setName(student.getName+"---A");
	studentDao.add(student);
	//Call B method
	studentService.B(student);
}
@Override
@Transactional(rollbackFor=Exception.class,propagation=Propagation.NOT_SUPPORTED)
public void B(Student student){
 	String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
	student.setName(student.getName+"---B");
	//Call B method
	studentDao.add(student);
    throw new RuntimeException("B Method threw an exception");
}
// Output. Although there are transactions in the callee and exceptions are thrown, the data will not be rolled back
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
 Transactions in callee: com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest
java.lang.RuntimeException: B Method threw an exception

The results of the above methods: Method B succeeded in adding and method A failed. B threw A RuntimeException

3.6 MANDATORY

MANDATORY means MANDATORY and obligatory. The literal meaning can also clearly express the characteristics of the transaction communication mechanism. There must be transactions, and exceptions are thrown if there are no transactions.

This propagation behavior is that I should be in the transaction anyway. If the current method is in the transaction, it will be added to the current transaction. If it is not in the transaction, it will throw an exception.

  1. There are transactions in the current method
    @Override
    @Transactional
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transaction in Caller:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //Communication services
        titleService.titleTransaction();
    }
    
    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("Transactions in callee:{}", transactionName);
        titleMapper.insert(new Title().setName("Chapter I"));
    }
    
    // Output, and the callee is added to the current transaction
     Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
     Transactions in callee: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
    
  2. No transaction exists for the current method
@Override
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //Communication services
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.MANDATORY)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
}
// Output, throw exception if there is no transaction
 Transaction in Caller: null
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

3.7 NEVER

There can be no transaction, and an exception will be thrown if there is a transaction.
This propagation method is that no matter who calls me, you are not allowed to have transactions, otherwise I will throw an exception.

  1. The caller has a transaction
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //Communication services
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.NEVER)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
}
// Output. If the caller has a transaction, the callee throws an exception
 Transaction in Caller: com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

The method execution of both the caller and the callee failed. Because the transaction propagation mechanism of the callee is NEVER and the caller has a transaction, when the caller calls the callee, the callee throws an exception and the transaction will be rolled back. The exception is thrown to the caller, who also has a transaction, so it also fails.

  1. Caller has no transaction
@Override
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transaction in Caller:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //Communication services
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.NEVER)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("Transactions in callee:{}", transactionName);
    titleMapper.insert(new Title().setName("Chapter I"));
}
// Output: the caller has no transaction. Although the callee has a transaction, it will execute in a transaction free manner
 Transaction in Caller: null
 Transactions in callee: com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest

4. Summary

I see a picture on the Internet, which is very well summarized. I also share it with you here.

Next, let's summarize how the caller and callee operate transactions under various communication modes. Note: method A is the caller and method B is the callee. For method A, there are two cases: transaction and no transaction, while for method B, there are seven cases. Let's see how method B operates transactions in each case.

Keywords: Java JavaEE Spring Back-end

Added by Fender963 on Wed, 23 Feb 2022 09:28:36 +0200