This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021.

Propagation behavior

The first aspect of transaction is propagation Behavior. When a transaction method is called by another transaction method, you must specify how the transaction should propagate. For example, a method may continue to run in an existing transaction, or it may start a new transaction and run in its own transaction. Spring defines seven propagation behaviors:

Propagation behavior meaning
PROPAGATION_REQUIRED The current method must run in a transaction and join the transaction if it exists. Otherwise, a new transaction is created.
PROPAGATION_SUPPORTS No transaction context is required, and if a transaction exists, the transaction is joined. Otherwise, it continues in a nontransactional manner.
PROPAGATION_MANDATORY The current method must run in a transaction and join the transaction if it exists. Otherwise, an exception is thrown. (Mandatory: mandatory)
PROPAGATION_REQUIRES_NEW Indicates that the current method must run in its own transaction. A new transaction will be started. If there is a current transaction, it is suspended during the execution of the method. If you use JTATransactionManager, you need to access TransactionManager
PROPAGATION_NOT_SUPPORTED Indicates that the method should not run in a transaction. If there is a current transaction, it will be suspended while the method is running. If you use JTATransactionManager, you need to access TransactionManager
PROPAGATION_NEVER Indicates that the current method should not run in a transaction context. If a transaction is currently running, an exception is thrown
PROPAGATION_NESTED Indicates that the method will run in a nested transaction if one already exists. Nested transactions can be committed or rolled back independently of the current transaction. If the current transaction does not exist, it behaves as PROPAGATION_REQUIRED. Note that vendor support for this propagation behavior varies.

Break down

PROPAGATION_REQUIRED

(1) PROPAGATION_REQUIRED The current transaction is supported if a transaction exists. If there are no transactions, start a new one.

// The transaction attribute PROPAGATION_REQUIREDMethodA {... methodB(); ... }// The transaction attribute PROPAGATION_REQUIREDMethodB {... }Copy the code

Using Spring declarative transactions, Spring uses AOP to support declarative transactions. Based on transaction attributes, it automatically decides whether to start a transaction before a method is called and whether to commit or roll back a transaction after the method is executed. Call methodB method alone:

main{
    methodB();
}
Copy the code

Is equivalent to:

Main{
    Connection con=null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        // method call
        methodB();
        // Commit the transaction
        con.commit();
    } catch(RuntimeException ex) {
        // Rollback the transaction
        con.rollback();
    } finally {
        // Release resourcescloseCon(); }}Copy the code

Spring guarantees that all calls to the methodB method get the same connection. When methodB is called, no transaction exists, so a new connection is obtained and a new transaction is opened. When MethodA is called separately, MethodB is called within MethodA. The implementation effect is equivalent to:

main{
    Connection con = null;
    try{
        con = getConnection();
        methodA();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally{ closeCon(); }}Copy the code

When MethodA is called, there are no transactions in the environment, so start a new transaction. When MethodB is called in MethodA, there is already a transaction in the environment, so MethodB joins the current transaction.

PROPAGATION_SUPPORTS

(2) PROPAGATION_SUPPORTS the current transaction if a transaction exists. If there is no transaction, non-transactional execution. However, for transactionally-synchronized transaction managers, PROPAGATION_SUPPORTS is slightly different from not using transactions.

// The transaction attribute PROPAGATION_REQUIRED
methodA(){
    methodB();
}

// Transaction attribute PROPAGATION_SUPPORTSMethodB () {... }Copy the code

When methodB is simply called, the methodB method is non-transactional. When methdA is called, methodB is added to methodA’s transaction and executed transactionally.

PROPAGATION_MANDATORY

(3) PROPAGATION_MANDATORY, the current transaction is supported if a transaction already exists. If there is no active transaction, an exception is thrown.

// The transaction attribute PROPAGATION_REQUIRED
methodA(){
    methodB();
}

// Transaction attribute PROPAGATION_MANDATORYMethodB () {... }Copy the code

When methodB is called alone, an exception is thrown because there is currently no active transaction

Throw new IllegalTransactionStateException (the “Transaction propagation ‘mandatory’ but no existing Transaction found”);

When methodA is called, methodB is added to methodA’s transaction and executed transactionally.

PROPAGATION_REQUIRES_NEW

(4) Always open a new transaction, PROPAGATION_REQUIRES_NEW If a transaction already exists, suspend the existing transaction.

// The transaction attribute PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

// The transaction attribute PROPAGATION_REQUIRES_NEWMethodB () {... }Copy the code

Call the A method:

main(){
    methodA();
}
Copy the code

The equivalent of

main(){
    TransactionManager tm = null;
    try{
        // Get a JTA transaction manager
        tm = getTransactionManager();
        tm.begin();// Start a new transaction
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();// Suspend the current transaction
        try{
            tm.begin();// Restart the second transaction
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();// Commit the second transaction
        } catch(RunTimeException ex) {
            ts2.rollback();// Rollback the second transaction
        } finally {
            // Release resources
        }

        //methodB completes the first transaction
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();// Commit the first transaction
    } catch(RunTimeException ex) {
        ts1.rollback();// Rollback the first transaction
    } finally {
        // Release resources}}Copy the code

Here, I refer to TS1 as the outer transaction and TS2 as the inner transaction. As you can see from the above code, TS2 and TS1 are two separate transactions, unrelated to each other. The success of Ts2 does not depend on TS1. If methodA fails a doSomeThingB method after calling methodB, the methodB method is still committed. The results of any code other than methodB are rolled back. To use PROPAGATION_REQUIRES_NEW, you need to use JtaTransactionManager as the transaction manager.

PROPAGATION_NOT_SUPPORTED

(5) Always execute non-transactionally, PROPAGATION_NOT_SUPPORTED, and suspend any transactions that exist. To use PROPAGATION_NOT_SUPPORTED, you also need to use JtaTransactionManager as the transaction manager. (Code example as above, can be similarly deduced)

PROPAGATION_NEVER

(6) Always execute non-transactionally, PROPAGATION_NEVER, and throw an exception if an active transaction exists.

PROPAGATION_NESTED

(7) Execute a nested transaction, PROPAGATION_NESTED, if an active transaction exists. If there is no active transaction, then the TransactionDefinition. PROPAGATION_REQUIRED properties is carried out. This is a nested transactions, using JDBC 3.0 drivers, support only DataSourceTransactionManager as a transaction manager. A java.sql.Savepoint class with a JDBC driver is required. Some JTA transaction manager implementations may provide the same functionality. Use PROPAGATION_NESTED, still need to turn the PlatformTransactionManager nestedTransactionAllowed attribute set to true; The default value of nestedTransactionAllowed is false.

// The transaction attribute PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

// Transaction attribute PROPAGATION_NESTEDMethodB () {... }Copy the code

If the methodB method is called separately, the REQUIRED attribute is executed. If the methodA method is called, it does the following:

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
        	methodB();
        } catch(RuntimeException ex) {
        	con.rollback(savepoint);
        } finally {
        	// Release resources
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        // Release resources}}Copy the code

Before methodB is called, setSavepoint is called to save the current state to SavePoint. If methodB fails, the methodB method is restored to the previously saved state. Note, however, that the transaction is not committed, and all operations including methodB are rolled back if the subsequent code (doSomeThingB() method call) fails.

The nested transaction

A very important concept of nested transactions is that the inner transactions depend on the outer transactions. When an outer transaction fails, actions taken by the inner transaction are rolled back. The failure of the inner transaction does not cause the rollback of the outer transaction.

The difference between PROPAGATION_REQUIRES_NEW and PROPAGATION_REQUIRES_NEW

They are very similar in that they are like a nested transaction, and if there is no active transaction, a new one will be started. With PROPAGATION_REQUIRES_NEW, an inner and outer transaction are like two separate transactions. Once an inner transaction has committed, it cannot be rolled back by an outer transaction. The two matters do not affect each other. Two transactions are not a true nested transaction. It also requires the support of the JTA transaction manager.

A rollback of an outer transaction can cause a rollback of an inner transaction, PROPAGATION_NESTED. The exception of the inner transaction does not cause the rollback of the outer transaction, which is a true nested transaction.

PROPAGATION_REQUIRES_NEW Starts a new, environment-independent “internal” transaction. The transaction will be fully commited or rolled back without dependence on external transactions, it will have its own isolation scope, its own locks, etc. The external transaction is suspended when the internal transaction begins execution, and resumes when the internal transaction ends.

On the other hand, PROPAGATION_NESTED starts a “nested” transaction, which is a true subtransaction of an existing transaction. When a nested transaction starts executing, it takes a savepoint. If the nested transaction fails, we roll back to this savepoint. A nested transaction is part of an external transaction and is committed only after the external transaction is completed.

The biggest difference between PROPAGATION_REQUIRES_NEW and PROPAGATION_NESTED is that PROPAGATION_REQUIRES_NEW is completely a new transaction, If an external transaction is committed, nested transactions will also be committed. This rule also applies to roll back.

PROPAGATION_REQUIRED should be our first transaction propagation behavior. It satisfies most of our transaction needs.

Reference -Spring transaction mechanism details