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:
- The declared method is a non-
public
method,final
method,static
method - 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:
- 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.
- A transaction propagation type that
does not support transactions
is configured - 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:
- 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:
- Use
public
permissions to define methods, notstatic
, notfinal
- Exceptions must be
thrown upward
. Even if exception handling is required within the logic, a custom exception needs to be thrown. Explicit configuration
Transaction propagation and callback exceptions in Transactional, do not use the default configuration- 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:
- The declared method is a non-
public
method,final
method,static
method - Object internal
Self-call
- The exception was caught by the business code logic and
was not thrown upward
to the proxy object. - A transaction propagation type that
does not support transactions
is configured - The
exception type that needs to be rolled back is not configured correctly
- The database engine does not support transactions (nonsense)