Detailed interpretation of Spring's transaction propagation attribute

Posted by keane7 on Wed, 30 Oct 2019 10:53:53 +0100

To learn things, we need to integrate knowledge with practice. If we only know the theory but never practice it, we will not have a solid grasp of it. It is estimated that we will forget it in a few days. Next, we will practice together to learn the propagation properties of Spring transactions.

Propagation attribute

The propagation attribute defines the processing behavior when one transaction method encounters another transaction method. There are seven behaviors in total. The definitions are as follows

Transmissible value describe
PROPAGATION_REQUIRED 0 Support the current transaction. If not, create a new transaction.
PROPAGATION_SUPPORTS 1 Supports the current transaction. If not, it will not run in a transactional way.
PROPAGATION_MANDATORY 2 Support the current transaction. If there is no current transaction, throw an exception.
PROPAGATION_REQUIRES_NEW 3 Whether there is a current transaction or not, a new transaction will be created
PROPAGATION_NOT_SUPPORTED 4 Transaction is not supported. If there is currently a transaction, suspend the transaction and not run it as a transaction.
PROPAGATION_NEVER 5 Transaction is not supported, throw exception if any
PROPAGATION_NESTED 6 If there is a current transaction, start a new one in the current transaction

In fact, if we only look at the concept, it is very straightforward to explain the role of each communication. At this time, we will use specific examples to demonstrate the behavior under each communication attribute.

In this demonstration, we use H2 database, which is used in memory, so it's just right for us to demonstrate the transaction effect. We don't need to do other configurations. We create a new table. Just put the following statement in the schema.sql file. When the spring boot program starts, it will automatically create such a table for us in memory.

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

Before demonstration, we will define two classes FooService and BarService. We use the methods in BarService to call the methods in FooService.

Environmental preparation

Before the transaction demonstration, it can be divided into the following situations. According to the arrangement and combination, we can get the following eight situations

  • Caller: is there any transaction?
  • Caller: is there any exception?
  • Callee: whether there is a transaction * * (this is controlled by the propagation attribute) * * so it is not in the permutation
  • Callee: abnormal or not
Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
Yes Yes Yes
Yes Yes nothing
Yes nothing Yes
Yes nothing nothing
nothing Yes Yes
nothing Yes nothing
nothing nothing Yes
nothing nothing nothing

Exception class

RollbackException is an exception class defined by ourselves.

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;
    // Promotion? Required demo no transaction
    @Override
    public void testRequiredNoTransactional() throws RollbackException {
        fooService.testRequiredTransactional();
    }
}

caller

There are two methods defined in BarService, one with transaction and the other without transaction

// Affairs
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {
}
// No transaction
@Override
public void noTransactional() throws RollbackException {   
}

Next, we will learn the transaction propagation attribute according to the eight situations defined above in Russia.

PROPAGATION_REQUIRED

Under this propagation property, whether the callee creates a new transaction depends on whether the caller brings the transaction.

To understand the characteristics of this propagation property, we need to demonstrate two examples of the above eight situations.

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
  • In the first case, when the callee throws an exception, if the inserted data cannot be queried, it means that the callee creates a new transaction when the caller has no transaction.
  • In the second case, when the caller throws an exception, if the inserted data cannot be queried, it means that the callee will join the current transaction when the caller has a transaction.

Let's first look at the method example of the callee's class.

@Service
public class FooServiceImpl implements FooService {
    @Autowired
    private JdbcTemplate jdbcTemplate;    
    // REQUIRED propagation property - exception thrown by callee
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredHasException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")");
        throw new RollbackException();
    }
    // REQUIRED propagation property - no exception thrown by callee
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredNoException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
    }
}

Let's take a look at an example of the caller method

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;
    // Affairs
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hasTransactional() throws RollbackException {
        // The caller has transactions, throw exceptions the callee has no exceptions
        fooService.testRequiredNoException();
        throw new RollbackException();
    }
    // No transaction
    @Override
    public void noTransactional() throws RollbackException {
        // The caller has no transaction, and the callee does not throw exceptions. The callee has exceptions.
        fooService.testRequiredHasException();
    }
}

At this point, we query when the program is called

String noException = Global.REQUIRED_NO_EXCEPTION;
String hasException = Global.REQUIRED_HAS_EXCEPTION;
try {
    barService.noTransactional();
}catch (Exception e){
    log.info("The first situation {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
}
try {
    barService.hasTransactional();
}catch (Exception e){
    log.info("The second situation {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
}

View printed logs

2019-10-16 13:02:04.142  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : Case 1 0
2019-10-16 13:02:04.143  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : Second case 0

We see that we have not found the corresponding data, indicating that the data has been rolled back. At this time, we should understand that sentence to support the current transaction. If not, create a new transaction.

PROPAGATION_SUPPORTS

Whether the callee has a transaction depends entirely on the caller. If the caller has a transaction, then there is a transaction. If the caller has no transaction, then there is no transaction.

Let's use the above two examples for demonstration.

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
  • The first case: when the callee throws an exception, if the data can still be queried, it means that the transaction has not been rolled back, and the callee has no transaction.
  • The second case: if the caller throws an exception, if no data can be found, the two methods are in the same transaction.

The following is still an example

The callee only changes the propagation attribute in the @ Transactional annotation to Propagation.SUPPORTS

// SUPPORTS propagation property - exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// SUPPORTS propagation property - no exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
public void testSupportsNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}

The caller is the same as the above example call. Let's see the execution effect directly.

2019-10-16 13:50:27.738  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : Case 1
2019-10-16 13:50:27.741  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : Second case 0

We see that in the first case, the data is found, indicating that in the first case, the callee has no transaction. At this time, we should understand this sentence. Support the current transaction. If not, it will not run as a transaction.

PROPAGATION_MANDATORY

It's still these two examples.

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
  • Case 1: because the caller has no transaction, exceptions should be thrown under this propagation attribute.
  • The second case: the transaction of the callee is the same as that of the caller

Next is the code example of the callee

// MANDATORY propagation property - exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// MANDATORY propagation property - no exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
public void testMandatoryNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
}

The caller is the same as the above example call. Let's see the execution effect directly.

2019-10-16 13:58:39.178 ERROR 12317 --- [           main] c.e.t.t.TransactionApplication           : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
2019-10-16 13:58:39.276  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : Case 1 0
2019-10-16 13:58:39.281  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : Second case 0

We find that, as we speculate, the callee will not create a new transaction by himself. At this time, we should understand that this sentence supports the current transaction, and throw exceptions if there is no transaction at present.

PROPAGATION_REQUIRES_NEW

Under this propagation attribute, whether the caller has a transaction or not, the callee will create a new transaction.

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
  • Case 1: the caller has no transaction, and the callee will create a new transaction, so no data can be found.
  • The second situation: if the caller has a transaction, the callee will create a new transaction, so the exception thrown by the caller will not affect the callee, so the data can be found.

Next we show the code.

Caller

// Requests? New propagation property - exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// Requests? New propagation property - no exception thrown by the callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')");
}

The caller's example is the same as above. Let's take a look at the implementation directly.

2019-10-16 16:29:20.296  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : Case 1 0
2019-10-16 16:29:20.298  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : Second case 1

We find that it is the same as our inference that the transaction of the caller has nothing to do with the transaction of the callee. At this point, we should understand that no matter whether there is a current transaction or not, there will be a new one.

PROPAGATION_NOT_SUPPORTED

The callee does not run as a transaction, regardless of whether the caller has a transaction or not

The same two examples

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
  • The first situation: the callee will not have transactions, so the corresponding data can be found after throwing exceptions
  • The second case: when the caller has transactions, the callee will also run in a transaction free environment, so we can still find the data.

Let's test our guess.

// Not supported propagation property - exception thrown by callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')");
    throw new RollbackException();
}
// Not supported propagation property - no exception thrown by the callee
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void testNotSupportNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')");
}

Then view the execution results

2019-10-16 16:38:35.065  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : Case 1
2019-10-16 16:38:35.067  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : Second case 1

We can see that we have found data in the last two cases. According to the demonstration effect, we can understand this sentence. Transaction is not supported. If there is a transaction at present, the transaction will be suspended and not run in transaction mode.

PROPAGATION_NEVER

If the caller has a transaction, the callee will throw an exception

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing

This is not a demonstration. I believe you will understand that in the first case, we can find the data. In the second case, the exception is thrown because the caller carries the transaction.

PROPAGATION_NESTED

Under this propagation attribute, the transaction of the callee is a subset of the transaction of the caller.

Let's focus on the characteristics of NESTED's propagation properties.

Does the caller have a transaction Explain
Yes The callee will start a new transaction, which is a nested relationship with the caller transaction
nothing The callee will start a new business by himself

We can use the following three examples to demonstrate what a nested transaction relationship is.

Does the caller have a transaction Is the caller abnormal Is the callee abnormal?
nothing nothing Yes
Yes Yes nothing
Yes nothing Yes
  • Case 1: if no data can be found, the callee will start a new transaction when the caller has no transaction.
  • Second, if no data can be found, it indicates that the outer transaction can affect the inner transaction.
  • Third, if data is found, it indicates that the inner transaction does not affect the outer transaction.

Next, we write the specific code.

// NESTED propagation properties - rollback transaction
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedHasException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')");
   // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    throw new RollbackException();
}
// NESTED propagation properties - do not roll back transactions
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
}

And then the next callers will be a little different.

@Override
@Transactional()
public void hasTransactionalNoException() throws RollbackException {
    // NESTED propagation attribute - the caller has transactions, the callee does not throw exceptions has exceptions
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
    fooService.testNestedHasException();
}

Then perform the effect

2019-10-16 18:01:06.387  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : Case 1 0
2019-10-16 18:01:06.389  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : Second case 0
2019-10-16 18:01:06.390  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : Third case 1

It can be seen that the essence of nested transactions is that the outer layer will affect the inner layer, and the inner layer will not affect the outer layer. The requirements "new" does not affect each other.

summary

Up to now, we have analyzed all seven kinds of communication attributes. From the beginning to the end of writing this article, we have also encountered some holes. Some of them don't know if they don't practice it by themselves. So I suggest the readers to practice and demonstrate all kinds of situations after reading this article. Only in this way can they get familiar with it.


Focus on the WeChat public. Programmer's dream ], focusing on Java, SpringBoot, SpringCloud, microservices, Docker, front and back end separation and other full stack technologies.

Topics: Java Attribute Spring Database SQL