Use @Transactional annotation to configure transaction management in Spring Boot

Transaction management is an essential part of application system development. Spring provides rich functional support for transaction management. Spring transaction management is divided into two ways: programmatic and declarative. Programmatic transactions refer to the realization of transactions through coding; declarative transactions are based on AOP, which decouples specific business logic from transaction processing. Declarative transaction management keeps business code logic from being polluted, so declarative transactions are used more in actual use. There are two ways for declarative transactions, one is to declare related transaction rules in the configuration file (xml), and the other is to use @Transactionalannotations . This article will focus on @Transactionalannotation -based transaction management.

A few things need to be clarified:

  1. By default Spring only rolls back runtime, unchecked exceptions (exceptions inherited from RuntimeException) or Errors. refer here
  2. @TransactionalAnnotations can only be applied to public methods. Refer here Method visibility and @Transactional

The following example uses mybatis, so spring boot will automatically configure one DataSourceTransactionManager. We only need to add @Transactionalannotations , and it will be automatically included in Spring's transaction management.

Simple to use

Just @Transactionalannotate .

There is a method for saving users as follows, adding @Transactionalannotations , using the default configuration, after throwing an exception, the transaction will be automatically rolled back, and the data will not be inserted into the database.

@Transactional
@Override
public void save() {
    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

We can see this information from the log

TIM screenshot 20171129135813

Introduction to the attributes of the @Transactional annotation

@TransactionalSeveral properties of are described below .

value and transactionManager properties

They both mean the same thing. When multiple transaction managers are configured, you can use this property to specify which transaction manager is selected.

propagation property

The propagation behavior of the transaction, the default value is Propagation.REQUIRED.

Optional values ​​are:

  • Propagation.REQUIRED

    If a transaction currently exists, join the transaction, if there is no current transaction, create a new transaction.

  • Propagation.SUPPORTS

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

  • Propagation.MANDATORY

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

  • Propagation.REQUIRES_NEW

    Recreate a new transaction, suspending the current transaction if there is one.

  • Propagation.NOT_SUPPORTED

    Runs in a non-transactional manner, suspending the current transaction if there is a current transaction.

  • Propagation.NEVER

    Operates in a non-transactional manner, throwing an exception if there is currently a transaction.

  • Propagation.NESTED

    Same as Propagation.REQUIRED.

These concepts are a bit abstract to understand, and will be explained later with code examples.

isolation property

The isolation level of the transaction, the default value is Isolation.DEFAULT.

Optional values ​​are:

  • Isolation.DEFAULT

    Use the default isolation level of the underlying database.

  • Isolation.READ_UNCOMMITTED

  • Isolation.READ_COMMITTED

  • Isolation.REPEATABLE_READ

  • Isolation.SERIALIZABLE

timeout property

Transaction timeout, the default value is -1. If the time limit is exceeded but the transaction has not completed, the transaction is automatically rolled back.

readOnly property

Specifies whether the transaction is a read-only transaction, the default value is false; in order to ignore those methods that do not require a transaction, such as reading data, you can set read-only to true.

rollbackFor property

Used to specify the exception type that can trigger transaction rollback. Multiple exception types can be specified.

noRollbackFor property

Throws the specified exception type and does not roll back the transaction. You can also specify multiple exception types.

Code example of the propagation attribute of @Transactional

For example, in the following code, the save method first calls the method1 method, and then throws an exception, which will cause the transaction to be rolled back. The following two data will not be inserted into the database.

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

public void method1() {
    User user = new User("宫本武藏");
    userMapper.insertSelective(user);
}

Now there are requirements as follows, even if an exception is thrown after the save method, it cannot affect the data insertion of the method1 method. Perhaps many people think as follows, add a new transaction to the method1 page, so that method1 will be executed in this new transaction, and the original transaction will not affect the new transaction. For example, add the annotation @Transactional to the method1 method, set the propagation property to Propagation.REQUIRES_NEW, and the code is as follows.

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("宫本武藏");
    userMapper.insertSelective(user);
}

After running, it is found that the data is not inserted into the database. How fat four, looks very unscientific. Let's take a look at the log content first.

TIM screenshot 20171129150737

As can be seen from the log content, in fact, both methods are in the same transaction, and the method1 method does not create a new transaction.

This has to look at the official Spring documentation .

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

Approximate meaning: In the default proxy mode, only the target method can be intercepted by Spring's transaction interceptor if it is called from outside. Direct calls of two methods in the same class will not be intercepted by Spring's transaction interceptor. Just like the save method above directly calls the method1 method in the same class, the method1 method will not be intercepted by Spring's transaction interceptor. intercept. AspectJ can be used to replace the Spring AOP proxy to solve this problem, but it will not be discussed here.

To solve this problem, we can create a new class.

@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method1() {
        User user = new User("风魔小太郎");
        userMapper.insertSelective(user);
    }
}

Then call the otherService.method1 method in the save method

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

Now, the data of the otherService.method1 method is inserted successfully, the data of the save method is not inserted, and the transaction is rolled back.

Continue to look at the log content

TIM screenshot 20171129153731

As can be seen from the log, the transaction of the save method is first created. Since the propagation attribute of @Transactional of the otherService.method1 method is Propagation.REQUIRES_NEW, the transaction of the save method is then suspended, the transaction of the otherService.method1 method is re-created, and then The transaction of the otherService.method1 method commits, and then the transaction of the save method is rolled back. This confirms that only the target method can be intercepted by Spring's transaction interceptor if it is called externally.

A few more examples are below.

Then remove the @Transactional annotation of the save method, and the @Transactional annotation of otherService.method1 remains unchanged. As can be seen from the log, only one transaction of the otherService.method1 method will be created, and both data will be inserted.

@Autowired
private OtherService otherService;

//    @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

Then remove the @Transactional annotation of the save method, and change the save method to call the internal method1 method. It can be seen from the log that no transaction is created at all, and both data will be inserted.

//    @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("宫本武藏");
    userMapper.insertSelective(user);
}

In this way, several other propagation property values ​​are easier to understand.

@Transactional transaction implementation mechanism

When the application system calls @Transactionalthe target method, Spring Framework uses AOP proxy by default, and generates a proxy object when the code runs. According to @Transactionalthe attribute configuration information of , this proxy object determines whether @Transactionalthe target method is intercepted by TransactionInterceptorthe interceptor. During TransactionInterceptorinterception , a transaction will be created and added before the target method starts to execute, and the logic of the target method will be executed. Finally, according to whether there is an exception in the execution, the abstract transaction manager will be used to AbstractPlatformTransactionManageroperate the data source DataSourceto commit or roll back the transaction.

There are two types of Spring AOP proxies: CglibAopProxyand JdkDynamicAopProxy, for CglibAopProxyexample , theCglibAopProxy intercept method of its inner class needs to be called . DynamicAdvisedInterceptorFor JdkDynamicAopProxy, its invoke method needs to be called.

Spring-transaction-mechanis

As mentioned above, the framework of transaction management AbstractPlatformTransactionManageris , and the specific underlying transaction processing implementation is implemented by PlatformTransactionManagerthe concrete implementation class of , such as the transaction manager DataSourceTransactionManager. Different transaction managers manage different data resources DataSource, such as those that DataSourceTransactionManagermanage JDBC Connection.

Spring-TransactionManager-hierarchy-subtypes

Source address

References

Epilogue

Due to my limited knowledge and ability, if there is something unclear in the text, I hope you can point it out in the comment area to help me write the blog post better.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325458058&siteId=291194637