[SpringBoot Practice] Transaction and transaction propagation mechanism & failure reasons & suggestions for correct use of transactions

Article directory

  • 1 Overview
  • 2. Affairs and transaction communication
    • 2.1 Declarative transaction description
    • 2.2. Reasons for the failure of declarative transactions
    • 2.3.Transaction mechanism
    • 2.4. Reasons for transaction propagation failure
  • 3. Suggestions on transaction usage
  • 4. Summary

1. Overview

We often use transactions in our development work to ensure data consistency when adding, deleting, and modifying operations in the database. When using Spring to process transactions, if they are not used correctly, it is easy for transaction failures or transactions to occur. The problem of propagation failure causes the expected results to be inconsistent with the actual results.
In order to completely solve this “accidental” transaction failure problem, today we will combine theory and code practice to verify what circumstances will cause transaction failure, and summarize how to use transactions correctly.

Note: The transaction mechanism verified in this article is a declarative transaction using the @Transactional annotation.

2. Transactions and transaction propagation

Before doing verification, you need to understand two easily confused concepts in Spring: transactions and transaction propagation.

  • Transaction: A mechanism to maintain data consistency, with ACID characteristics.
  • Transaction propagation: Spring combines declarative transactions of different methods and obtains different results depending on the combination method.

2.1 Declarative transaction description

The following will briefly explain the implementation principle of declarative transactions in Spring, but before that, let’s understand programmatic transactions, which is the basis of declarative transactions. Here is a simplified Demo code:

@Autowired
private PlatformTransactionManager transactionManager;

public void performSomeBusinessLogic() {<!-- -->
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    
    try {<!-- -->
        //Execute business logic
        
        //Transaction submission
        transactionManager.commit(status);
    } catch (Exception ex) {<!-- -->
    //Transaction rollback
        transactionManager.rollback(status);
        throw ex;
    }
}

We can use the three APIs PlatformTransactionManager, TransactionDefinition, and TransactionStatus to complete programmatic transactions.

For declarative transactions, we can make this method run in a transactional manner by annotating the method with a @Transactional. This method is often used in Spring through AOP is implemented by using the @Transactional annotation as the cut-off point to enter a transaction processing aspect. What needs to be done in this aspect is to open the transaction, execute the business logic, and determine whether the transaction is submitted or not. rollback. It can be simply understood that the set of code for programmatic transactions is placed in the proxy object generated through AOP.

2.2. Reasons for failure of declarative transactions

Since it is implemented by proxy objects, we can draw a conclusion: if AOP fails, then declarative transactions will also fail, so we can find several reasons why transactions fail. reason:

  1. The declared method is a non-public method, final method, static method
  2. Object internal Self-call

Regarding point 2, assume that there are two methods in the current object, namely a and b, a does not have @Transactional written on it, b is written above. At this time, use this.b() in the a method to initiate the call. In this case, b is called. ‘s instance method is not a method in the proxy object. Without a proxy, the natural transaction will be invalid. This is also the reason why transaction propagation fails as mentioned below.

If you don’t want to put the two methods into two different classes, you can obtain the proxy again in the a method through applicationContext.getBean(this.getClass()); Object so it won’t become invalid.

In addition, looking at the above code again, the transaction will be rolled back by calling the rollback method in the catch block. If an exception is handled in the business process Without throwing upward into the proxy object, rollback will not be triggered, causing the rollback of the transaction to fail. Therefore, we can find another reason for the failure of the transaction:

  1. The exception was caught by the business code logic and was not thrown upward to the proxy object.

Regarding the reason for this failure, it needs to be added that in some scenarios we may perform unified exception handling on the Service layer through AOP through aspects. This situation also It may cause exceptions not to be thrown to transaction-related proxy objects, resulting in rollback failure. This situation is hidden deeply and may not be easy to detect.

In addition to the transaction failure caused by the AOP level, we will focus on the @Transactional itself. There are two important configurations in this annotation, transaction propagation configuration (Propagation) and Rollback exception configuration (rollbackFor), these two configuration errors may also cause transaction failure.

  1. A transaction propagation type that does not support transactions is configured
  2. The exception type that needs to be rolled back is not configured correctly

Regarding point 4, the following content will be described in detail. Here is the rollback exception configuration.
In the annotation class Transactional, there is such an annotation for rollbackFor:

This means that when the rollback exception is not explicitly specified, it will be rolled back only when the exception thrown is RuntimeException or Error is thrown, and otherwise Checked Exceptions are not rolled back.

Finally, there is the problem of the database itself:

  1. The database engine does not support transactions (nonsense)

2.3. Transaction propagation mechanism

Transaction propagation is when multiple methods with transactions are used in combination, hoping to achieve different results by configuring different propagation mechanisms. There are a total of 7 configurations of transaction propagation defined in Spring, which are:

  • REQUIRED: Multiple methods are run under the same transaction. If any error is reported, they will be rolled back together.
  • REQUIRED_NEW: A new transaction will be opened when called. Multiple methods will run in different transactions. If each method reports an error, it will only roll back itself and will not affect each other.
  • SUPPORT: The called method will join the caller’s transaction. If the caller does not have a transaction, it will run non-transactionally.
  • NOT_SUPPORTED: If the caller has a transaction, the transaction is suspended and the called method runs as a non-transaction.
  • MANDATORY: It is mandatory to have a transaction, if not, an exception will be thrown.
  • NESTED: Multiple methods belong to the same transaction, but the transaction of the called method belongs to the caller’s sub-transaction.
  • NEVER: Forces that there must be no transaction, if there is, an exception will be thrown.

Here we mainly talk about NESTED, which is a nested transaction. Different from REQUIRED, if the called method reports an error, the transaction will be rolled back to a savePoint will not cause the caller’s transaction to be rolled back. Unlike REQUIRED_NEW, if an exception occurs to the caller, it will also be rolled back with the callee.

In addition, when using the transaction propagation mechanism in a project, you must also consider the isolation level of the database. The combination of the two is likely to bring you a deadlock spree. Here is a simple example: use REQUIRED_NEW Transaction propagation, the caller and the callee both update the same row of data using id as the condition, the caller waits for the callee to complete execution, and the callee waits for the caller Completing transactions and waiting for each other leads to deadlock.

2.4. Reasons for transaction propagation failure

In fact, it has been mentioned in the transaction failure above that transaction failure will definitely propagate and fail, but what needs to be emphasized here is. The most easily overlooked reason is the self-calling of the objects used.
When we are writing projects, in many cases the two methods are placed in the same service object, and they are inadvertently called through this.xxx, and then If you look at it, you will find that transaction propagation is invalid.

3. Suggestions on transaction usage

When conditions permit, replacing declarative transactions with programmatic transactions can effectively avoid transaction failures and control transactions in a more fine-grained manner. However, the disadvantages are also obvious, such as intrusion into business logic. Too much non-business logic code.

If you must use declarative transactions, here are some suggestions for using transactions correctly based on some of the reasons mentioned above:

  1. Use public permissions to define methods, not static, not final
  2. Exceptions must be thrown upward. Even if exception handling is required within the logic, a custom exception needs to be thrown.
  3. Explicit configuration Transaction propagation and callback exceptions in Transactional, do not use the default configuration
  4. As long as multiple transaction method calls are involved, pay attention to the self-call issue and be sure to use a proxy object to call it.

4. Summary

This article mainly describes the problems that lead to transaction failure in Spring’s transaction practice and suggestions for the correct use of transactions. Finally, the reasons for transaction failure are listed here for subsequent reference:

  1. The declared method is a non-public method, final method, static method
  2. Object internal Self-call
  3. The exception was caught by the business code logic and was not thrown upward to the proxy object.
  4. A transaction propagation type that does not support transactions is configured
  5. The exception type that needs to be rolled back is not configured correctly
  6. The database engine does not support transactions (nonsense)