Propagation mode in database transaction

Posted by hailam on Wed, 15 Jan 2020 13:26:19 +0100

  • Promotion "required: if there is no transaction at present, a new transaction will be created. If there is a transaction at present, the transaction will be added. This setting is the most commonly used setting.

  • Promotion_supports: supports the current transaction. If there is a transaction, join it. If there is no transaction, execute it as a non transaction. '

  • Promotion ﹣ Management: supports the current transaction. If there is a transaction, join it. If there is no transaction, throw an exception.

  • Promotion menu requirements menu new: create a new transaction, no matter whether there is a transaction in the current store or not.

  • Promotion not supported: performs the operation in a non transactional manner, and suspends the current transaction if it currently exists.

  • Promotion ﹣ never: execute in a non transactional manner, throw an exception if there is currently a transaction.

  • Promotion ﹣ nested: if there is currently a transaction, it is executed within the nested transaction. If there is no transaction at present, perform the operation similar to the promotion [required].

The database is not easy to demonstrate here. I use spring to demonstrate it

This is a simulation operation of creating an order. When creating an order, the balance is deducted first and then the inventory is deducted;

Create two tables
create table pay
(
    id         int auto_increment
        primary key,
    pay_amount int not null comment 'Payment amount'
);

create table inventory
(
    id  int auto_increment
        primary key,
    num int not null comment 'Inventory quantity'
);

Create a springboot project
public class OrderService {
    @Resource
    private PayService payService;
    @Resource
    private InventoryService inventoryService;
    //Create order
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
}
public class PayService {
    @Resource
    private PayMapper payMapper;
    //payment
    @Transactional(rollbackFor = Exception.class)
    public void payment(Integer num) {
        payMapper.insert(num);
    }
}
public class InventoryService {
    @Resource
    private InventoryMapper inventoryMapper;
    //Deduct stock
    @Transactional(rollbackFor = Exception.class)
    public void deduction(Integer num) {
        inventoryMapper.insert(num);
        //Simulation failure
        System.out.println(1 / 0);
    }
}
    @Resource
    private OrderService orderService;
    //Call create order
    @Test
    public void test1() {
        orderService.createOrder(1);
    }    

REQUIRED

  • First, do not simulate the inventory deduction failure (note out the failure code); run the test class and find the database data as follows:

    id Stock
    1 1
    id payment
    1 1

    Both the payment table and the amount table have successfully inserted data;

  • Let's release the comment, simulate the inventory deduction failure, and run the test class:

    @Test
    public void test1() {
        orderService.createOrder(2);
    }

The discovery database data is as follows:
Neither the payment table nor the amount table was successfully inserted;

Here we check the default transaction propagation property in @ Transactional discovery

Propagation propagation() default Propagation.REQUIRED;

When creating an order, both payment and deduction inventory use the same transaction, so they both succeed or fail together.

REQUIRES_NEW

Let's use requirements? New to try the code

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void payment(Integer num) {
        payMapper.payment(num);
    }

Test class

    @Test
    public void test1() {
        orderService.createOrder(3);
    }

Here, the test class is still run when the order fails (release the comment), and the results are as follows:

id payment
3 3

We can see that we paid, but we didn't deduct the inventory. Because we used the requests ﹣ new transaction propagation i mode, it used the new transaction anyway, so it was a separate transaction, it succeeded, and later failed to deduct the inventory. These two independent transactions will not affect each other.

The above two transaction propagation modes are the most common and the only one that will reopen new transactions in addition to the NESTED mode.

MANDATORY

This propagation method can be seen from the name that it must exist in a transaction, otherwise an exception will be thrown.
Let's remove the open transaction method above create order

    //@Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
    //Call test class
    @Test
    public void test1() {
        orderService.createOrder(4);
    }

It can be found that the program directly reports an error and there is no data in the database

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

NEVER

This mode of communication is just the opposite of MANDATORY. Due to the limited space, no demonstration will be given here. The error message will be posted below

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

SUPPORTS

This mode of communication is very Buddhist. If there is one, it will be added. If there is one, it will not be added. The code is modified as follows:

//Comment out transaction opening
//    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
//Modify transaction propagation mode
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
    public void payment(Integer num) {
        payMapper.payment(num);
    }
//Test class
    @Test
    public void test1() {
        orderService.createOrder(5);
    }

By querying the database, you can find:

id payment
5 5

Payment has been made, but there is no inventory. Here, the payment is not added to any transaction, so there is no rollback.

NOT_SUPPORTED

This kind of transaction propagation mode will never be carried out in the way of transaction, skipping the execution of transaction. The code will not be demonstrated here.

NESTED

//Open transaction
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
//Using NESTED
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void payment(Integer num) {
        payMapper.payment(num);
    }
    //Test class
    @Test
    public void test1() {
        orderService.createOrder(6);
    }

Here we find that there is no data insertion in the database. The result here is the same as that of REQUIRED, but it actually opens a sub transaction in the current transaction. Next, we throw an exception in the payment and remove the exception of deducting inventory. Try again to see the result:

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
    //There may be some misunderstandings in capturing exceptions here. The previous REQUIRED can also catch exceptions here, but it can also be found that there will be failures here. If you use this trycatch here, there is a pit here. This problem will be explained in detail later. If you need to understand it, you can search by yourself: transaction rolled back because it has been marked as rollback only
    //Let's explain it a little bit: here, because we open a new transaction, we can manually capture the same.
    //But if we use REQUIRED, the order creation and payment are the same transaction. When the transaction is abnormal, it will be set to rollback only, but if you continue to operate, an exception will be reported.
    //Here is an article: [transaction rolled back because it has been marked as rolled only] (https://blog.csdn.net/f641385712/article/details/80445912)
        try {
            payService.payment(num);
        }catch (Exception ignore){

        }
        inventoryService.deduction(num);
    }
    @Test
    public void test1() {
        orderService.createOrder(6);
    }

Here we can see that there is no deduction record in the database, but the inventory is deducted; because the payment is a nested transaction, this transaction will not affect the external transaction. (for knowledge about nested transactions, please search by yourself)

Digression:
The AOP used by spring is not implemented in the same service, so if we call the same service, the method that is called will not work even if @Transactional is added, so we can call it in different service or we call it another way:

//The two methods are in the same class
//Call this way
((InventoryService) AopContext.currentProxy()).deduction2(num);
//Business will take effect here
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void deduction2(Integer num) {
        inventoryMapper.insert(-num);
//        System.out.println(1/0);
    }
//The maven configuration of aspects needs to be introduced here. Spring aspects can also
//        <dependency>
//            <groupId>org.springframework</groupId>
//            <artifactId>spring-aspects</artifactId>
//        </dependency>

So far, the introduction of the propagation properties of the transaction ends.

Published 24 original articles, won praise 3, visited 60000+
Private letter follow

Topics: Database Spring SpringBoot Maven