@Transctional transaction propagation mechanism (Propagation)

[Foreword] Transactional Propagation is the process mechanism of how transactions are propagated between these methods when multiple transactional methods call each other. This article will introduce the seven propagation behavior mechanisms of transactions in Spring.

PART1, transaction propagation enumeration


Spring transaction propagation type enumeration Propagation defines seven types, namely:

  • REQUIRED: If there is a current transaction, join the transaction; if there is no current transaction, create a new transaction.
  • SUPPORTS: If there is a transaction currently, join the transaction; if there is no transaction currently, continue execution in a non-transactional manner.
  • MANDATORY: If there is a transaction currently, join the transaction; if there is no transaction currently, throw an exception.
  • REQUIRES_NEW: Create a new transaction, if there is a current transaction, suspend the current transaction.
  • NOT_SUPPORTED: Perform operations in a non-transactional manner. If there is a current transaction, suspend the current transaction.
  • NEVER: Perform operations in a non-transactional manner, and throw an exception if a transaction currently exists.
  • NESTED: Execute within a nested transaction if there is a current transaction. If no transaction currently exists, a new transaction is created. If the main transaction commits, all nested transactions are committed. If any nested transaction fails, all nested transactions are rolled back without affecting the commit of the main transaction.

These seven types are defined as enumerations in the Spring source code. The source code can be seen in the Propagation under the org.springframework.transaction.annotation package:
insert image description here
Let's insert data into two different tables as an example, create two insertA and insertB method:

public void insertA(Data a) {
    
    
	// 插入tableA的操作
	insertToA(a);
} 

public void insertB(Data b) {
    
    
    // 插入tableB的操作
	insertToB(b);
}

Note: In Spring, transactions are implemented based on AOP, that is, using proxy classes. If method A without @Transactional annotation in the same class internally calls method B with @Transactional annotation, the transaction of method B with @Transactional annotation will be invalid. The reason is that in the process of self-invocation, the class itself calls instead of the proxy object to call, so AOP will not be generated, that is, the transaction will become invalid. Spring transaction management takes effect only when the method annotated with @Transactional is called outside the class.

Next, in order to prevent the problem that the internal method call of the class fails to pass the proxy call, resulting in transaction failure, we use AopContext.currentProxy() to obtain the proxy class and then call it to create a test class:

// 前置:@EnableAspectJAutoProxy(exposeProxy = true) 配置exposeProxy
@Component
public class Test{
    
    
    
	public void testMain(){
    
    
		insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB(); 
	}

	public void testB(){
    
    
		// 参数b1插入tableB的操作
    	insertToB(b);
    	throw new RuntimeExecption();
    	// 参数b2插入tableB的操作
    	insertToB(b2);
	}
}

In the above pseudo-code, without adding any transaction, call the testMain() test method, data a and data b are inserted into table A and table B respectively, but data b2 cannot be inserted into table B, in specific application scenarios , will bring about problems such as data consistency, so the importance of transactions is self-evident.

PART2, detailed explanation of the transmission mechanism


1. REQUIRED (Spring's default transaction propagation type)

If there is a current transaction, join the transaction; if there is no current transaction, create a new 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),

This diagram can facilitate our understanding of the REQUIRED propagation mechanism:
insert image description here

Continue to reuse the above scenario, Example 1:

@Component
public class Test{
    
    
     
	public void testMain(){
    
    
		insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB(); 
	}
    
	@Transcational(propagation = Propagation.REQUIRED)
	public void testB(){
    
    
		// 参数b1插入tableB的操作
    	insertToB(b);
    	throw new RuntimeExecption();
    	// 参数b2插入tableB的操作
    	insertToB(b2);
	}
}

At this time, there is no transaction on the testMain method, the transaction is declared on the testB method, and the propagation behavior is REQUITED, then when testMain calls testB, a new transaction will be created .

Therefore, inserting data a in table A in testMain will be executed successfully, but storing data b in table B will cause the transaction to roll back due to an exception, and b and b2 cannot be stored in table B.

Example 2:

public class Test{
    
    
	
    @Transcational(propagation = Propagation.REQUIRED)
	public void testMain(){
    
    
		insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB();  
	}
    
	@Transcational(propagation = Propagation.REQUIRED)
	public void testB(){
    
    
		// 参数b1插入tableB的操作
    	insertToB(b);
    	throw new RuntimeExecption();
    	// 参数b2插入tableB的操作
    	insertToB(b2);
	}
}

At this time, the transaction is declared on both the testMain method and the testB method, and the propagation behavior is REQUITED. Then, when testMain is executed, a new transaction will be created , and when testB is executed, because there is currently a transaction, it will be added to the transaction of testMain .

Therefore, if data a is stored in testMain and data b is stored in testB, the transaction will be rolled back due to an exception, so that the relevant data will not be stored and changed in the database.

2、SUPPORT

If there is currently a transaction, join the transaction; if there is no current transaction, continue execution in a non-transactional manner.

	/**
	 * 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,
	 * {@code SUPPORTS} is slightly different from no transaction at all,
	 * as it defines a transaction scope that synchronization will apply for.
	 * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
	 * will be shared for the entire specified scope. Note that this depends on
	 * the actual synchronization configuration of the transaction manager.
	 * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
	 */
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

Continue to reuse the above scenario, example:

public class Test{
    
    
    
	public void testMain(){
    
    
		insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB();  
	}
    
	@Transcational(propagation = Propagation.SUPPORT)
	public void testB(){
    
    
		// 参数b1插入tableB的操作
    	insertToB(b);
    	throw new RuntimeExecption();
    	// 参数b2插入tableB的操作
    	insertToB(b2);
	}
}

At this time, the testMain method does not declare a transaction, but the testB method declares a transaction, and the propagation behavior is SUPPORT. When testMain calls testB, because the current transaction does not exist, testB will continue to execute in a non-transactional manner.

Therefore, there is no transaction rollback as a whole, data a and data b will be stored, and data b2 cannot be stored due to an exception.

Other situations: If the transaction flag is also declared on testMain, and it is the default propagation method, all data will be rolled back.

3、MANDATORY

If there is currently a transaction, join the transaction; if there is no current transaction, throw an exception.

	/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

Example:

public class Test{
    
    
    
	public void testMain(){
    
    
		insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB();  
	}
    
	@Transcational(propagation = Propagation.MANDATORY)
	public void testB(){
    
    
		// 参数b1插入tableB的操作
    	insertToB(b);
    	throw new RuntimeExecption();
    	// 参数b2插入tableB的操作
    	insertToB(b2);
	}
}

At this time, data a will be stored successfully. When testB is executed because there is no transaction in testMain, an exception will be thrown, and data b and b2 will not be stored in the database.

Other situations: If testMain also declares a transaction flag, and it is the default propagation method, then testB will join the transaction of testMain, and all data will be rolled back.

4、REQUIRES_NEW

Create a new transaction, or suspend the current transaction if there is a current transaction.

insert image description here

In other words, the propagation method is to create a new transaction regardless of whether the current transaction exists or not.

	/**
	 * 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
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code jakarta.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Jakarta EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

Example:

public class Test{
    
    
 
    @Transcational(propagation = Propagation.REQUIRED)
    public void testMain(){
    
    
        insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB(); 
        throw new RuntimeExecption();
    }

    @Transcational(propagation = Propagation.REQUIRES_NEW)
    public void testB(){
    
    
        // 参数b1插入tableB的操作
        insertToB(b);
        // 参数b2插入tableB的操作
        insertToB(b2);
    }
}

At this time, both testMain and testB declare transactions, the propagation behavior of testMain is REQUIRED, and the propagation behavior of testB is REQUIRES_NEW. When testMain calls testB, testB will create a new transaction, which is independent of the transaction of testMain.

Therefore, in the above example, data b and b2 will be stored successfully, but data a will be rolled back due to an exception in the testMain method, causing data a to fail to be stored.

5、NOT_SUPPORTED

Perform operations in a non-transactional manner, and suspend the current transaction if there is a current transaction.

This propagation method indicates that the current method does not support transaction rollback. At execution time, regardless of whether there is currently a transaction, it will run in a non-transactional manner.

	/**
	 * 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
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code jakarta.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Jakarta EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

Example:

public class Test{
    
    
 
    @Transcational(propagation = Propagation.REQUIRED)
    public void testMain(){
    
    
        insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB();    
    }

    @Transcational(propagation = Propagation.NOT_SUPPORTED)
    public void testB(){
    
    
        // 参数b1插入tableB的操作
        insertToB(b);
        throw new RuntimeExecption();
        // 参数b2插入tableB的操作
        insertToB(b2);
    }
}

In the above example, because the propagation mode of the testB transaction is NOT_SUPPORTED, this method does not perform transaction rollback. When testB is executed, after the storage of data b is completed, an exception will be thrown, causing the transaction of the testMain method to roll back. But testB doesn't do the rollback.

Therefore, data b is stored successfully, but data a and b2 fail to be stored due to transaction rollback and abnormal interruption.

6、NEVER

Perform the operation in a non-transactional manner, throwing an exception if a transaction currently exists.

This propagation method strictly marks the characteristics that the current method does not support transactions, which is exactly the opposite of MANDATORY.

	/**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	NEVER(TransactionDefinition.PROPAGATION_NEVER),

Examples are as follows:

public class Test{
    
    

    @Transcational
    public void testMain(){
    
    
        insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB();   
    }

    @Transcational(propagation = Propagation.NEVER)
    public void testB(){
    
    
        // 参数b1插入tableB的操作
        insertToB(b);
        // 参数b2插入tableB的操作
        insertToB(b2);
    }
}

Since there is a transaction statement in testMain, and the transaction propagation mode of testB is NEVER, current transactions are not allowed.

So the above example throws an exception and all data storage fails.

7、NESTED

Executes within a nested transaction, if a transaction currently exists. If no transaction currently exists, a new transaction is created.

If the main transaction commits, all nested transactions are committed. If any nested transaction fails, all nested transactions are rolled back without affecting the commit of the main transaction.

Example 1:

public class Test{
    
    

    @Transcational(propagation = Propagation.REQUIRED)
    public void testMain(){
    
    
        insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        test.testB(); 
        throw new RuntimeExecption();
    }

    @Transcational(propagation = Propagation.NESTED)
    public void testB(){
    
    
        // 参数b1插入tableB的操作
        insertToB(b);
        // 参数b2插入tableB的操作
        insertToB(b2);
    }
}

testB declares the nested transaction propagation method, and testMain throws an exception at the end of execution, which will cause the main transaction to roll back, and the nested transaction in testB will be accompanied by rollback.

So all data is not stored successfully.

Example 2:

public class Test{
    
    

    @Transcational(propagation = Propagation.REQUIRED)
    public void testMain(){
    
    
        insertA(a); 
        Test test = (Test)AopContext.currentProxy();
        try {
    
    
        	test.testB(); 
        } catch (Exception e) {
    
    
        	// ...
        }
        
    }

    @Transcational(propagation = Propagation.NESTED)
    public void testB(){
    
    
        // 参数b1插入tableB的操作
        insertToB(b);
        throw new RuntimeExecption();
        // 参数b2插入tableB的操作
        insertToB(b2);
       
    }
}

testB declares the nested transaction propagation method, and testB throws an exception at the end of execution, which will cause the nested transaction to roll back, but will not affect the rollback of the main transaction.

Therefore, a data storage succeeds, b data and b2 data storage fail.

Other situations: In this example, if we change the propagation type of testB to REQUIRED, even if an exception is caught when testMain is called, the entire transaction will still be rolled back, because the caller and the called party share the same transaction.

Summarize


The @Transactional annotation can be applied to interfaces, interface methods, classes, and class methods. When applied to a class, all public methods of that class will have the transaction attribute of this type. When acting at the method level, it overrides class-level definitions. When acting on interfaces and interface methods, it will only take effect when using interface-based proxies, that is, JDK proxies.

In Spring, the transaction propagation type has an impact on method execution order and results. The type of transaction propagation defined by the caller and callee methods together determine the outcome of the code.

ps. Two questions:

Q1: When the internal method of the class is called, avoid @Transcational failure to obtain the proxy class method to be verified.

Q2: When MANDATORY propagation and NEVER propagation meet the exception triggering conditions of the propagation mechanism, should the exception be thrown when testMain is executed, or when testB is called? To be verified.

Guess you like

Origin blog.csdn.net/zhzh980/article/details/129975886