18 details of Spring transaction propagation mechanism

What is the transaction propagation mechanism

The propagation mechanism of transactions, as the name suggests, is how to propagate transactions between multiple transaction methods.

For example, method A is A transactional method. Method B is called when method A executes. Whether method B has A transaction or not and whether it needs A transaction will have different effects on method A and method B, and this effect is determined by the transaction propagation mechanism of the two methods.

Propagation property propagation enumeration

Spring's propagation mechanism for transactions http://​ Propagation​ Seven classifications are defined in the enumeration:

  • REQUIRED default
  • SUPPORTS support
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED is not supported
  • NEVER
  • NESTED

The propagation mechanism of transactions is specified by spring. Because in development, the simplest transaction is that the business code is under the same transaction, which is also the default propagation mechanism. If an error is reported, all data will be rolled back. However, when dealing with complex business logic, the calls between methods have the following requirements:

  • The called method needs to add a new transaction, and the new transaction and the original transaction are independent.
  • The method called does not support transactions
  • The method invoked is a nested transaction

Detailed explanation of seven communication mechanisms

First, create two methods A and B to insert data. Insert data A:

public class AService {
    public void A(String name) {
        userService.insertName("A-" + name);
    }

}
Copy code

Insert data B:

public class BService {
    public void B(String name) {
        userService.insertName("B-" + name);
    }

}
Copy code

Create mainTest method and childTest method with pseudo code

   public void mainTest(String name)  {
        // Deposit a1
        A(a1);
        childTest(name);
    }
Copy code

main calls the test method, where

   public void childTest(String name)  {
        // Deposit b1
        B(b1);
       throw new RuntimeException(); 
       // Deposit b2
       B2(b2);
    }
Copy code

The above pseudo code calls the mainTest method. If neither mainTest nor childTest uses transactions, what is the result of data storage?

Because no transaction is used, both a1 and b1 are saved successfully, and b2 will not execute after throwing an exception. Therefore, both a1 and b1 insert data, while b2 does not insert data.

REQUIRED (default transaction)

      /**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>This is the default setting of a transaction annotation.
	 */
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
Copy code

If no transaction currently exists, create a new transaction. If a transaction exists, it is added to the current transaction. This is a default transaction.

Example 1: according to the scenario, for example, add a transaction in childTest and set the propagation attribute to REQUIRED. The pseudo code is as follows:

   public void mainTest(String name)  {
        // Deposit a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name)  {
        // Deposit b1
        B(b1);
       throw new RuntimeException(); 
    }
Copy code

The new transaction a1 is not successful because it is another transaction a1. In childTest, because an exception is thrown, b2 will not be added, but b1 will be added and rolled back. Finally, a1 was added successfully, and b1 was not added successfully.

Example 2: add transactions in both mainTest and childTest, and the propagation attribute is REQUIRED. The pseudo code is as follows:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {
        // Deposit a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name) {
        // Deposit b1
        B(b1);
       throw new RuntimeException(); 
   }
Copy code

According to the REQUIRED propagation attribute, if there is a transaction, it will be added to the current transaction. Both methods belong to the same transaction. If an exception occurs in the same transaction, all methods will be rolled back. Therefore, a1 and b1 were not added successfully.

SUPPORTS

/**
	 * Support a current transaction, execute non-transactionally if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>Note: For transaction managers with transaction synchronization,
	 */
Copy code

If there is currently no transaction, it runs in a non transactional manner. If a transaction exists, it is added to the current transaction.

Example 3: childTest adds a transaction, and the propagation attribute is set to SUPPORTS. The pseudo code is as follows:

   public void mainTest(String name) {
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {
        B(b1);
       throw new RuntimeException(); 
    }
Copy code

The propagation attribute is SUPPORTS. If there is no transaction, it will run in a non transaction manner. It indicates that neither method uses transactions. Without transactions, a1 and b1 are added successfully.

Example 4: mainTest adds a transaction and sets the propagation property to REQUIRED. childTest adds a transaction and sets the propagation attribute to SUPPORTS. The pseudo code is as follows:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {
        B(b1);
       throw new RuntimeException(); 
       B2(b2);
    }
Copy code

SUPPORTS propagates the attribute. If there is a transaction, it will be added to the current transaction. mainTest and childTest belong to the same transaction, but childTest throws an exception. The addition of a1 and b1 is rolled back. Finally, the addition of a1 and b1 fails.

MANDATORY

/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
Copy code

If a transaction exists, it is added to the current transaction. If there is no transaction, an error is reported. This means that if you want to call the method of propagating the attribute of MANDATORY, there must be a transaction, otherwise an error will be reported.

MANDATORY has similar function restrictions. It must be called by the method with transaction, or an error will be reported.

Example 5: first, add a transaction in childTest and set the propagation attribute to MANDATORY. The pseudo code is as follows:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
      B2(b2);
   }
Copy code

Direct error reporting on the console:

No existing transaction found for transaction marked with propagation 'mandatory'
Copy code

The description is marked as mandatory. If no transaction is found in the propagation attribute, an error will be reported directly. Because mainTest has no transaction, a1 is added successfully. childTest failed to b1 add due to an error.

Example 6: mainTest adds a transaction and sets the propagation property to REQUIRED. childTest adds a transaction and sets the propagation attribute to MANDATOR. The pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }
Copy code

If there is a transaction, the transaction is added to the current transaction. childTest throws an exception in the same transaction, and the addition of a1 and b1 is rolled back, so the addition of a1 and b1 fails.

REQUIRES_NEW

   /**
	 * Create a new transaction, and suspend the current transaction if one exists.
	 * Analogous to the EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 */
Copy code

Create a new transaction. Suspend transactions if they exist.

A new transaction is created regardless of whether there is a transaction or not.

Example 7: childTest adds a transaction and sets the propagation attribute to requirements_ New, pseudo code is as follows:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }
Copy code

There is no transaction in mainTest, a1 is added successfully, childTest creates a new transaction, reports an error, and rolls back b1. Therefore, a1 was added successfully, while b1 failed.

Example 8: mainTest adds a transaction and sets the propagation property to REQUIRED. childTest adds a transaction and sets the propagation attribute to requirements_ New, pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }
Copy code

mainTest creates a transaction and childTest creates a new transaction. In the childTest transaction, exception is thrown and b1 is rolled back. Exception is thrown to mainTest method and a1 is rolled back. Finally, a1 and b1 are rolled back.

Example 9: in example 8, if you don't want requirements_ The new propagation attribute affects the called transaction, and the exception capture will not affect the called transaction.

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       try {
              childTest(name);
       } catch (Exception e) {
           e.printStackTrace();
       }   
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }
Copy code

childTest threw an exception and caught it in mainTest, which had no impact on mainTest. Therefore, b1 was rolled back, b1 failed to be added, and a1 was added successfully.

Example 10: mainTest sets the propagation property to REQUIRED and throws an exception in mainTest. childTest also sets requirements_ New propagation attribute, pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
       throw new RuntimeException();    
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);  
       B2(b2);
   }
Copy code

childTest is a newly created transaction. As long as no exception is thrown, it will not be rolled back. Therefore, b1 is added successfully, while mainTest throws an exception, and a1 fails to be added.

REQUIRES_ Exceptions will be thrown from mainTest to the caller, but not from childTest.

NOT_SUPPORTED

  /**
	 * Execute non-transactionally, suspend the current transaction if one exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 */
Copy code

Whether or not there is a current transaction, it runs in a non transactional manner.

Example 11: childTest adds a transaction and sets the propagation attribute to NOT_SUPPORTED, pseudo code is as follows:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }
Copy code

NOT_SUPPORTED is executed in a non transactional manner. There is no transaction in childTest, and b1 is added successfully. The mainTest also has no transaction, and a1 is added successfully.

Example 12: childTest adds a transaction and sets the propagation attribute to NOT_SUPPORTED, mainTest add the default propagation attribute REQUIRED, and the pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
    }
Copy code

childTest is executed in a non transactional manner, and b1 is added successfully. The mainTest has a transaction, which is rolled back after an error is reported, and a1 addition fails.

NEVER

   /**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
Copy code

Do not use transactions. If there are transactions, throw an exception.

The NEVER method does not use transactions. Call the NEVER method and throw an exception if there are transactions.

Example 13: childTest adds the NEVER propagation attribute. The pseudo code is as follows:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
      B2(b2);
   }
Copy code

NEVER uses transactions and mainTest does not use transactions, so a1 and b1 are added successfully and b2 fails.

Example 14: mainTest adds the REQUIRED propagation attribute, and childTest sets the propagation attribute to NEVER. The pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }
Copy code

There is a transaction in mainTest, which causes childTest to report an error, b1 fails to add, childTest throws an error to mainTest, and a1 fails to add.

NESTED

/**
	 * Execute within a nested transaction if a current transaction exists,
	 * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
	 * <p>Note: Actual creation of a nested transaction will only work on specific
	 */
Copy code

If the current transaction exists, a nested transaction is run. If there is no transaction, create a new transaction just like REQUIRED.

Example 15: childTest sets the NESTED propagation attribute. The pseudo code is as follows:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }
Copy code

Setting the NESTED propagation attribute in childTest is equivalent to creating a new transaction, so b1 fails to be added, mainTest has no transaction, and a1 is added successfully.

Example 16: set the mainTest propagation property to REQUIRED, create a new transaction, and throw an exception at the end of the method. The childTest property is set to NESTED, and the pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
       throw new RuntimeException();     
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
       B(b1);         
      B2(b2);
   }
Copy code

childTest is a nested transaction. When the main transaction throws an exception, the nested transaction is also affected, that is, a1, b1 and b2 fail to be added. Unlike example 10, example 10 does not affect childTest transactions.

  • Needed and required_ Differences between new:
    • REQUIRED_NEW is to start a new transaction, which has nothing to do with the called transaction. The caller rolls back without affecting REQUIRED_NEW transaction.
    • NESTED is a NESTED transaction and a sub transaction of the caller. If the caller's transaction is rolled back, NESTED will also roll back.

Example 17: as in example 16, set the propagation attribute to REQUIRED in mainTest and NESTED in childTest. The difference is that the exception thrown by childTest is caught in mainTest. The pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
      try {
           childTest(name);
       } catch (RuntimeException e) {
           e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
      B(b1);         
      B2(b2);
      throw new RuntimeException(); 
   }
Copy code

childTest is a sub transaction. It reports an error and rolls back. Both b1 and b2 failed to be added. mainTest catches the exception and is not affected by the exception. a1 is added successfully.

Example 18: modify example 2. mainTest catches the exception thrown by childTest. The pseudo code is as follows:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
      try {
           childTest(name);
       } catch (RuntimeException e) {
           e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.REQUIRED)
  public void childTest(String name) {
      B(b1);         
      B2(b2);
      throw new RuntimeException(); 
   }
Copy code

Both mainTest and childTest methods are in the same transaction. If one method reports an error, it will be rolled back and captured. Transaction rolled back because it has been marked as rollback only exception will be thrown if data is added to the whole transaction. For the same transaction, some transactions cannot be rolled back or some do not, either rolled back together or executed successfully. Therefore, all data failed to be added.

  • Comparing example 17 and example 18, the difference between needed and REQUIRED:
    • The REQUIRED propagation attribute indicates that both the caller and the callee use the same transaction, and the callee has an exception. No matter whether the exception is caught or not, because it belongs to the same transaction, as long as an exception occurs, the transaction will be rolled back.
    • The NESTED callee has an exception. As long as the exception is caught, only the callee's transaction is rolled back, and the caller's transaction is not affected.

summary

Propagation propertiessummary
REQUIREDBy default, all transactions are under the same transaction, and exceptions occur, regardless of whether all transactions are rolled back or not
SUPPORTSIf there is no transaction, it will run in a non transaction mode. If there is a transaction, it will join the transaction
MANDATORYForce the caller to add a transaction. If there is no transaction, an error will be reported, and if there is a transaction, it will join the transaction
REQUIRES_NEWNo matter whether the caller has a transaction or not, a new transaction will be created, and the caller exception will not affect requirements_ New transaction
NOT_SUPPORTEDNo matter whether the caller has a transaction or not, it is executed in a non transactional manner, and the exception will be rolled back
NEVERNo transaction is needed, and an error will be reported if a transaction exists, which is the opposite of MANDATORY
NESTEDNested transaction: create a new sub transaction. The transaction execution is independent of each other. If the caller has an exception, it will be rolled back directly

Test source code

Original link: https://juejin.cn/post/7054340368124346399

Keywords: Java Spring Back-end

Added by rajan on Thu, 17 Feb 2022 11:16:16 +0200