Transactional default exception rollback and transaction propagation behavior

It is convenient to use Spring to manage database transactions by simply introducing the @Transactional annotation in the proxy object to enable transactions. Transactional Transactional (set rollbackFor); Transactional Transactional (set Propagation) By default, runtimeExceptions and errors are rolled back. The default transaction propagation behavior is to support the current transaction or create a new transaction if none exists. The default behaviors above can cover a large number of application scenarios and are convenient to use. You can adapt to a large number of application scenarios without setting parameters. However, when more than two services use transactions, you need to know more about transaction propagation behavior.

Transaction propagation behavior defined in Spring

In the Spring in the org. Springframework. Transaction. TransactionDefinition defined in the transaction propagation behavior, a total of seven kinds of behavior, can be divided into three categories according to support the current transaction or not.

  1. Support current transactions
    1. PROPAGATION_REQUIRED: If a transaction currently exists, it is used. If there is no transaction currently, a new transaction is created.
    2. PROPAGATION_SUPPORTS: If a transaction currently exists, it is used, if no transaction currently exists, it is still run in a non-transactional manner.
    3. PROPAGATION_MANDATORY: Uses the transaction if it currently exists, and throws an exception if it does not
  2. Current transactions are not supported
    1. PROPAGATION_REQUIRES_NEW: Always create a new transaction and suspend the current transaction if one exists
    2. PROPAGATION_NOT_SUPPORTED: runs nontransactionally, suspending the current transaction if one exists
    3. PROPAGATION_NEVER: runs transactionally, throwing an exception if a transaction is currently in place
  3. The nested transaction
    1. PROPAGATION_NESTED: Creates a nested transaction to run. If no transaction exists, the behavior is the samePROPAGATION_REQUIRED

The above is the description of the seven transaction propagation behaviors, which will be abstract and not easy to understand. The following is a demonstration and description of the three common transaction propagation behaviors REQUIRED,REQUIRED_NEW, and NESTED service combinations.

Examples of use of common transaction propagation behavior

1 Upstream services REQUIRED, downstream services do not add transactions

The upstream service

    @Transactional(rollbackFor = Exception.class)
    public void createUser(a) {

        User user = new User();
        user.setAge(19).setName("test1").setVersion(1);
        userMapper.insert(user);

        logService.saveLog("createUser");
       
    }
Copy the code

The downstream service throws an exception:

 public void saveLog(String method) {
        SystemLog entity  = new SystemLog();
        entity.setMethod(method);
        systemLogMapper.insert(entity);
        throw new RuntimeException("Test rollback saveLog without adding transaction,createUser Propagation.REQUIRED");

    }
Copy the code

The current transaction takes effect and the current service createUser and the downstream service saveLog are rolled back.

2 The current service REQUIRED and the downstream service REQUIRES_NEW

2.1 Downstream Services Throw exceptions

The code of the upstream service is unchanged. The code of the downstream service is modified as follows

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String method) {
        SystemLog entity  = new SystemLog();
        entity.setMethod(method);
        systemLogMapper.insert(entity);
        throw new RuntimeException("Test rollback saveLog Propagation.REQUIRES_NEW,createUser Propagation.REQUIRED");

    }
Copy the code

The current transaction takes effect and the current service createUser and the downstream service saveLog are rolled back. The transaction of the current service is suspended, the downstream transaction is rolled back, and exceptions can cause the upstream transaction to be rolled back. So it’s still going to roll back.

2.2 The upstream Service Throws an exception

Put the operation that threw the exception into the upstream service, and the downstream service returns to its normal logic, modifying the code as follows

    @Transactional
    public void createUser(a) {

        User user = new User();
        user.setAge(19).setName("test1").setVersion(1);
        userMapper.insert(user);

        logService.saveLog("createUser");

        throw new RuntimeException("Upstream service createUser Propagation.REQUIRED Raises an exception" +
                "Downstream service saveLog REQUIRES_NEW");

    }


    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String method) {
        SystemLog entity  = new SystemLog();
        entity.setMethod(method);
        systemLogMapper.insert(entity);
    }
Copy the code

[saveLog] [saveLog] [saveLog] [saveLog] [saveLog] [saveLog] [saveLog] The reason is that when the downstream transaction is configured as REQUIRES_NEW to execute the downstream service code, the current transaction is suspended, the downstream transaction executes, the downstream transaction does not have any exception, executes smoothly, returns to the upstream transaction, the upstream transaction encounters an exception, and the upstream transaction rolls back.

A downstream transaction uses REQUIRES_NEW, and the upstream and downstream are two separate transactions. A downstream commit failure does not directly affect the upstream transaction, except in the first case, when the downstream service throws an exception, it is also thrown to the upstream service, and the upstream service is rolled back.

3 USES REQUIRES_NEST

Scenarios that use nested transactions have two requirements:

  1. Successful federation: The upstream transaction is required to commit with the downstream transaction, that is, the downstream transaction is committed only if the upstream transaction successfully commits. thisPROPAGATION_REQUIREDIt can be done.
  2. Isolation failure: Rollback of the downstream transaction is required without affecting the commit of the upstream transaction. This requirement is simply called “quarantine failure.” thisPROPAGATION_REQUIRES_NEWIt can be done.

If requirement 1 is met with PROPAGATION_REQUIRED, the rollback of the downstream transaction unconditionally rolls back the upstream transaction. If requirement 2 is not met, the commit failure of the downstream transaction does not affect the upstream transaction.

Requirement 2 is satisfied with PROPAGATION_REQUIRES_NEW, but the downstream transaction is a completely new transaction context, and the success of the upstream transaction does not affect the downstream commit at all, which does not satisfy Requirement 1.

Need to use NESTED to meet upstream affects downstream, downstream does not affect upstream

Code examples:

3.1 Upstream services use the PROPAGATION_REQUIRED mechanism to throw exceptions, and downstream services use the NESTED mechanism

The upstream service@Transactional
    public void createUser(a) {

        User user = new User();
        user.setAge(19).setName("test1").setVersion(5);
        userMapper.insert(user);

        logService.saveLog("createUser");
        throw new RuntimeException("Upstream throws exceptions, downstream NESTED mechanism"); } Downstream service@Transactional(propagation = Propagation.NESTED)
    public void saveLog(String method) {
        SystemLog entity  = new SystemLog();
        entity.setMethod(method);
        systemLogMapper.insert(entity);
    }
Copy the code

If upstream rolls back, downstream rolls back.

3.2 Upstream services use the PROPAGATION_REQUIRED mechanism, and downstream services use the NESTED mechanism to throw exceptions

The upstream service@Transactional
    public void createUser(a) {

        User user = new User();
        user.setAge(19).setName("test1").setVersion(5);
        userMapper.insert(user);
        try {
            logService.saveLog("createUser");
        }catch(Exception e) {}} downstream service@Transactional(propagation = Propagation.NESTED)
    public void saveLog(String method) {
        SystemLog entity  = new SystemLog();
        entity.setMethod(method);
        systemLogMapper.insert(entity);
        throw new RuntimeException("Downstream throws exceptions using the NESTED mechanism");
    }
Copy the code

The upstream commit is normal and the downstream rollback does not affect the upstream commit.

conclusion

Here are seven propagation behaviors for Spring transactions, divided into three categories that support current transactions: PROPAGATION_REQUIRED,PROPAGATION_SUPPORTS,PROPAGATION_MANDATORY The following transactions are not supported :PROPAGATION_REQUIRES_NEW,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER, and NESTED transactions :NESTED. PROPAGATION_REQUIRE PROPAGATION_REQUIRES_NEW Describes the three most common propagating behaviors