Spring several kinds of transaction understanding test

  • Propagation properties

    • Environmental preparation

    • Exception class

    • Caller

  • to sum up

  • Github address


To learn things, you must know and do things together. If you only know the theory and have not practiced it, then you will not be particularly solid. It is estimated that you will forget it in a few days. Next, let's practice together to learn the propagation properties of Spring transactions.

Propagation properties

The propagation attribute defines the processing behavior when a transaction method encounters another transaction method. There are seven kinds of behaviors, which are defined as follows

In fact, just looking at the concept has already explained the role of each communicability very straightforwardly. At this time, we will use specific examples to demonstrate the behavior of each communicable attribute.

In this demonstration, we use the H2 database, which is used in memory, so it is just right for us to demonstrate the effect of the transaction. We don’t need to make other configurations. Let’s create a new table. Put the following statement in the schema.sql file, and the SpringBoot program will automatically create such a table in memory for us when it starts.

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

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

Environmental preparation

Before carrying out the transaction demonstration, it can be divided into the following situations. According to the permutation and combination, we can draw the following eight situations

  • Caller: whether there is a transaction

  • Caller: Is there an exception?

  • Callee: Whether there is a transaction ** (this is controlled by the propagation attribute) ** So it is not in the permutation and combination

  • Callee: Is there an exception?

Exception class

The RollbackException is an exception class defined by ourselves

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;

     // PROPAGATION_REQUIRED演示 无事务
    @Override
    public void testRequiredNoTransactional() throws RollbackException {
        fooService.testRequiredTransactional();
    }
}

Caller

Define two methods in BarService, one with transaction and the other without transaction

// 有事务
@Override
@Transactional(rollbackFor = Exception.class)
public void hasTransactional() throws RollbackException {

}

// 无事务
@Override
public void noTransactional() throws RollbackException {

}

Next, we will study the transaction propagation attributes based on the eight situations defined above in Russia.

PROPAGATION_REQUIRED

Under this propagation attribute, whether the callee creates a new transaction depends on whether the caller carries a transaction.

To understand the characteristics of this propagation attribute, in fact, it is enough for us to demonstrate two examples of the above eight situations.

  • In the first case, when the callee throws an exception, if the inserted data cannot be queried, it means that the callee has created a new transaction without the caller.

  • In the second case, when the caller throws an exception, if the inserted data cannot be queried, it means that the callee has joined the current transaction when the caller has a transaction.

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

@Service
public class FooServiceImpl implements FooService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    // REQUIRED传播属性-被调用者有异常抛出
    @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传播属性-被调用者无异常抛出
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void testRequiredNoException() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
    }
}

Next we look at an example of the caller method

@Service
public class BarServiceImpl implements BarService{
    @Autowired
    private FooService fooService;

    // 有事务
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void hasTransactional() throws RollbackException {
        // 调用者有事务,抛异常  被调用者无异常
        fooService.testRequiredNoException();
        throw new RollbackException();
    }

    // 无事务
    @Override
    public void noTransactional() throws RollbackException {
        // 调用者无事务,不抛异常  被调用者有异常
        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("第一种情况 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
}

try {
    barService.hasTransactional();
}catch (Exception e){
    log.info("第二种情况 {}",
            jdbcTemplate
                    .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
}

View the printed log

2019-10-16 13:02:04.142  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 13:02:04.143  INFO 11869 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

We see that we have not found the corresponding data, indicating that the data has been rolled back. At this point, we should understand that sentence supports 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, there will be a transaction, and the caller has no transaction if there is no transaction.

Next, we will use the above two examples to demonstrate

  • The first case: if the callee throws an exception, if the data can still be queried, it means that the transaction has not been rolled back, indicating that the callee has no transaction

  • The second case: if the caller throws an exception, if the data cannot be found, it means that the two methods are in one transaction

The following is still an example demonstration

The callee just replaces the propagation attribute in the @Transactional annotation with Propagation.SUPPORTS

// SUPPORTS传播属性-被调用者有异常抛出
@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传播属性-被调用者无异常抛出
@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, we directly view the execution effect

2019-10-16 13:50:27.738  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 1
2019-10-16 13:50:27.741  INFO 12174 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

We saw that the data was found in the first case, indicating that the callee has no transaction in the first case. At this point we should understand that this sentence supports the current transaction, if not, it will not run in a transactional manner.

PROPAGATION_MANDATORY

These two examples are still used for demonstration

  • The first case: because the caller does not have a transaction, an exception should be thrown under this propagation attribute

  • The second case: the transaction of the callee and the transaction of the caller are the same

Next is the code example of the callee

// MANDATORY传播属性-被调用者有异常抛出
@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传播属性-被调用者无异常抛出
@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, we directly view the execution effect

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           : 第一种情况 0
2019-10-16 13:58:39.281  INFO 12317 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0

We found that the same as we speculated, it means that the callee will not create a new transaction by himself. At this time, we should understand that this sentence supports the current transaction. If there is no transaction currently, an exception will be thrown.

PROPAGATION_REQUIRES_NEW

Under this propagation attribute, regardless of whether the caller has a transaction, the callee will create a new transaction

  • The first case: the caller has no transaction, the callee will create a new transaction, so no data can be found

  • The second case: the caller has a transaction, the callee will create a new transaction, so the caller throws an exception without affecting the callee, so the data can be found

Next we demonstrate the code.

Callee

// REQUIRES_NEW传播属性-被调用者有异常抛出
@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();
}

// REQUIRES_NEW传播属性-被调用者无异常抛出
@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 the one above, let’s look directly at the execution

2019-10-16 16:29:20.296  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 16:29:20.298  INFO 15553 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 1

We find that it is the same as our deduction, indicating that the affairs of the caller and the callee are completely unrelated. At this point we should understand this sentence, regardless of whether there is a transaction currently, a new transaction will be started.

PROPAGATION_NOT_SUPPORTED

Regardless of whether the caller has a transaction, the callee does not run in a transactional manner

The same two examples

  • The first case: the callee will not have a transaction, then the corresponding data can be found after the exception is thrown

  • The second case: when the caller has a transaction, the callee will also run in a transaction-free environment, so we can still find the data

Next, verify our guess

// NOT_SUPPORTED传播属性-被调用者有异常抛出
@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传播属性-被调用者无异常抛出
@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 result

2019-10-16 16:38:35.065  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 1
2019-10-16 16:38:35.067  INFO 15739 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 1

We can see that the data was found in the last two cases. According to the demonstration effect, this sentence should be understood. Transaction is not supported. If there is a transaction currently, the transaction will be suspended and not run as a transaction.

PROPAGATION_NEVER

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

I won’t demonstrate this. I believe everyone who sees it here should understand that we can find the data in the first case. In the second case, because the caller is carrying a transaction, an exception will be thrown.

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

Regarding the relationship between nested transactions, we can demonstrate with the following three examples.

  • The first case: if the data cannot be found, it means that the callee will start a new transaction if the caller has no transaction

  • The second case: If no data is found, it means that the outer affairs can affect the inner affairs

  • The third case: If the data is found, it means that the inner affairs do not affect the outer affairs

Next we write specific code

// NESTED传播属性-回滚事务
@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传播属性-不回滚事务
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testNestedNoException() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
}

Then the next caller will be a little different

@Override
@Transactional()
public void hasTransactionalNoException() throws RollbackException {
    // NESTED传播属性 - 调用者有事务,不抛异常  被调用者有异常
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
    fooService.testNestedHasException();
}

Then execute the effect

2019-10-16 18:01:06.387  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第一种情况 0
2019-10-16 18:01:06.389  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第二种情况 0
2019-10-16 18:01:06.390  INFO 17172 --- [           main] c.e.t.t.TransactionApplication           : 第三种情况 1

It can be seen that the essence of nested transactions is that the outer layer affects the inner layer, and the inner layer does not affect the outer layer. And REQUIRES_NEW does not affect each other.

to sum up

Up to now, we have all analyzed the seven communication attributes. From the beginning to the end of this article, we have encountered some pits. Some of them are not known at all if you don’t practice it yourself, so I still recommend readers to read this article. In the future, I will practice and demonstrate various situations by myself. Only in this way can I be familiar with it.

Github address

https://github.com/modouxiansheng/Doraemon

Guess you like

Origin blog.csdn.net/qq_39809613/article/details/106994709