What is the transaction propagation mechanism

The propagation mechanism of a transaction, as the name implies, is the invocation of multiple transaction methods and how transactions propagate between these methods.

For example, method A is A transactional method, and method B is called when method A executes, so whether or not method B needs transactions will have different influences on method A and method B, and this influence is determined by the transaction propagation mechanism of the two methods.

Propagation attribute Enumeration

Spring defines seven categories in the Propagation enumeration:

  • REQUIRED by default
  • SUPPORTS support
  • MANDATORY enforcement
  • The REQUIRES_NEW new
  • NOT_SUPPORTED does not support
  • NEVER NEVER
  • NESTED NESTED

The propagation mechanism for transactions is specified by Spring. Because in development, the simplest transaction is that the business code is all under the same transaction, this is also the default propagation mechanism, if an error occurs, all data is rolled back. But when dealing with complex business logic, calls between methods have the following requirements:

  • The invoked method requires a new transaction, which is separate from the original transaction.
  • The method called does not support transactions
  • The method called is a nested transaction

Detailed explanation of 7 transmission mechanisms

Insert (A); insert (B);

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

Insert data B:

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

Create mainTest and childTest methods using pseudocode

Public void mainTest(String name) {// Store a1 A(a1); childTest(name); }Copy the code

Main calls the test method, where

Public void childTest(String name) {void childTest(String name); throw new RuntimeException(); // store b2 b2 (b2); }Copy the code

If both mainTest and childTest do not use transactions, what is the result of the data store?

Since no transaction is used, a1 and B1 are saved successfully, and b2 will not execute after the exception is thrown. So a1 and B1 both insert data, and B2 doesn’t 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 the code

If no transaction exists, create a new one. If a transaction exists, join the current transaction. This is a default transaction.

Example 1: Add a transaction to childTest and set the propagation property to REQUIRED, as an example for the scenario:

Public void mainTest(String name) {// Store a1 A(a1); childTest(name); } @ Transactional (propagation = propagation. The REQUIRED) public void childTest (String name) {/ / in the b1 B (b1); throw new RuntimeException(); }Copy the code

A1 was added successfully because mainTest has no transaction and childTest creates a new transaction. In childTest, b2 is not added because an exception was thrown, while B1 is added and rolled back. Finally, A1 was added successfully, but B1 was not.

Example 2: Add a transaction to both mainTest and childTest with the propagation attribute REQUIRED, with the following pseudocode:

@transactional (Propagation = propagation.REQUIRED) public void mainTest(String name) {void Transactional(propagation = propagation. childTest(name); } @ Transactional (propagation = propagation. The REQUIRED) public void childTest (String name) {/ / in the b1 B (b1); throw new RuntimeException(); }Copy the code

Propagates the REQUIRED attribute to join the current transaction, if it exists. Both methods belong to the same transaction, and if an exception occurs in the same transaction, both methods are rolled back. So a1 and B1 didn’t add 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 the code

If there is no transaction, it runs in a non-transactional manner. If a transaction exists, join the current transaction.

Example 3: childTest adds a transaction with the propagation property set to SUPPORTS. The pseudocode looks like this:

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

The propagation attribute is SUPPORTS, and if there is no transaction, it runs in a non-transactional manner. If no transaction is used, a1 and B1 are added successfully.

Example 4: mainTest adds a transaction and sets the propagation property to REQUIRED. ChildTest adds the transaction and sets the propagation attribute to SUPPORTS. The pseudocode looks like this:

   @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 the code

SUPPORTS propagates to join the current transaction if it exists. MainTest and childTest belong to the same transaction. ChildTest throws an exception, and a1 and B1 additions are rolled back. A1 and B1 additions fail.

MANDATORY

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

If a transaction exists, join the current transaction. If no transaction exists, an error is reported. This means that if you want to call a method with the MANDATORY propagation attribute, you must have a transaction, or an error will be reported.

MANDATORY is similar to a function constraint. It must be called by a transactional method or an error is reported.

Example 5: First add a transaction to childTest and set the propagation property to MANDATORY with the following pseudocode:

  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 the code

Error in console:

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

An error is reported if the transaction is not found when the transaction is marked as mandatory. A1 was added successfully because mainTest has no transaction. ChildTest failed to add B1.

Example 6: mainTest adds a transaction and sets the propagation property to REQUIRED. ChildTest adds transaction and sets propagation property to MANDATOR with pseudocode 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 the code

If a transaction exists, it is added to the current transaction. In the same transaction, childTest raised an exception, a1 and B1 additions were rolled back, a1 and B1 additions failed.

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 the code

Create a new transaction. If a transaction exists, suspend the transaction.

A new transaction is created whether there is one or not.

Example 7: childTest adds a transaction and sets the propagation attribute to REQUIRES_NEW with the following pseudocode:

  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 the code

MainTest does not have a transaction, a1 was added successfully, childTest created a transaction, error, rollback B1. Therefore, a1 is added successfully, but B1 fails to be added.

Example 8: mainTest adds a transaction and sets the propagation property to REQUIRED. ChildTest adds the transaction and sets the propagation attribute to REQUIRES_NEW with the following pseudocode:

  @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 the code

MainTest creates a transaction, and childTest creates a new transaction. In the childTest transaction, an exception is thrown, b1 is rolled back, the exception is thrown to mainTest, a1 is also rolled back, and eventually both A1 and B1 are rolled back.

Example 9: In Example 8, if you do not want the REQUIRES_NEW propagation attribute to affect the invoked transaction, catching the exception does not affect the invoked 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 the code

ChildTest raised an exception, which was caught in mainTest, but had no impact on mainTest, so 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 on mainTest. ChildTest also sets the REQUIRES_NEW propagation attribute, with the following pseudocode:

  @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 the code

ChildTest is a newly created transaction and will not be rolled back unless an exception is thrown. B1 was added successfully, while mainTest raised an exception but a1 failed.

The REQUIRES_NEW propagation attribute, if it has an exception, only affects the caller from the called party, and the caller does not affect the caller. That is, childTest throws an exception that affects mainTest, and mainTest does not throw an exception to 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 the code

Runs in a non-transactional manner, whether or not there is a current transaction.

Example 11: childTest adds a transaction and sets propagation property to NOT_SUPPORTED with the following pseudocode:

  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 the code

NOT_SUPPORTED is executed in a non-transactional manner. ChildTest does not have a transaction. B1 was added successfully. MainTest also has no transaction and A1 has been added successfully.

Example 12: childTest adds the transaction, sets the propagation attribute to NOT_SUPPORTED, and mainTest adds the default propagation attribute REQUIRED with the following pseudocode:

  @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 the code

ChildTest was executed in non-transaction mode, and B1 was added successfully. MainTest has a transaction and is rolled back after an error is reported. A1 fails to be added.

NEVER

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

No transactions are used, and an exception is thrown if one exists.

The NEVER method does not use transactions and throws an exception if a transaction exists.

Example 13: childTest adds the NEVER propagation property with the following pseudocode:

  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 the code

NEVER does not use transactions. MainTest does not use transactions. A1 and b1 are added successfully.

Example 14: mainTest adds the REQUIRED propagation attribute and childTest sets the propagation attribute to NEVER, with the following pseudocode:

  @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 the code

MainTest has a transaction, so childTest fails to add B1, childTest fails to add A1, mainTest fails to add B1.

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 the code

If the current transaction exists, a nested transaction is run. If one does not exist, create a new transaction as REQUIRED.

Example 15: childTest sets the NESTED propagation property with the following pseudocode:

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

When the NESTED propagation attribute was set in childTest, a new transaction was created. B1 failed to be added, mainTest did not have a transaction, and A1 was 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. ChildTest sets the property to NESTED, with the following pseudocode:

  @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 the code

ChildTest is a nested transaction. When the main transaction throws an exception, the nested transaction is affected, that is, A1, B1, and B2 fail to be added. Unlike example 10, example 10 does not affect childTest transactions.

  • NESTED and REQUIRED_NEW
    • REQUIRED_NEW starts a new transaction, regardless of the invoked transaction. The REQUIRED_NEW transaction is not affected by the caller rollback.
    • NESTED is a NESTED transaction that is a child of the caller. If the caller’s transaction is rolled back, NESTED is also rolled back.

Example 17: Same as in example 16, in mainTest set the propagation attribute to REQUIRED, and in childTest set the propagation attribute to NESTED. However, in mainTest, the following code is used to catch exceptions thrown by childTest:

  @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 the code

ChildTest is a child transaction, error rollback, b1 and B2 failed to add. MainTest captures the exception and is not affected by the exception. A1 is added successfully.

Example 18: Adapting example 2, mainTest catches an exception thrown by childTest with the following pseudocode:

  @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 the code

Both mainTest and childTest methods are in the same transaction, and if either method returns an error, it is rolled back and caught. Transaction rolled back because it has been marked as rollback-only; Transaction rolled back because it has been marked as rollback-only Otherwise, the execution succeeds. Therefore, all data fails to be added.

  • contrastThe sample of 17andThe sample of 18The difference between nested-nested and REQUIRED
    • The REQUIRED propagation attribute indicates that both the caller and the called are using the same transaction, and that the called gets an exception, regardless of whether the exception is caught or not, because it belongs to the same transaction, and whenever an exception occurs, the transaction is rolled back.
    • When an exception occurs on the called party, only the called party’s transaction is rolled back if the exception is caught.

conclusion

Propagation properties conclusion
REQUIRED By default, all transactions are under the same transaction and an exception occurs, regardless of whether all transaction rollback is captured
SUPPORTS If there is no transaction, it runs in a non-transactional manner, and if there is, it joins the transaction
MANDATORY Forces the caller to add a transaction, an error if none exists, and joins the transaction if one exists
REQUIRES_NEW A new transaction is created whether or not the caller has one, and the REQUIRES_NEW transaction is not affected by the caller exception
NOT_SUPPORTED Regardless of whether or not the caller has a transaction, the execution is non-transactional, and exceptions are rolled back
NEVER An error is reported if a transaction exists, as opposed to MANDATORY
NESTED Nested transactions, create a new sub-transaction, transaction execution is independent of each other, if the caller exception, directly rolled back

The test source

  • Spread attribute source code

reference

  • Take a look at Spring transactions – the propagation mechanism for transactions

If you find this article helpful, please give it a thumbs up!