Hello, this is AKA Baiyan who loves Coding, Hiphop and drinking a little wine.
Database transaction is an indispensable knowledge point in back-end development. In order to better support our database operations, Spring supports two transaction management methods in the framework:
- Programmatic transaction
- Declarative transaction
In everyday business development, we use declarative transactions using the @Transactional annotation.
In general use, Spring does a pretty good job of implementing database ACID (note that Spring only does programming transactions, and ultimately data transactions are implemented by the database).
But as long as human code is written, it's bound to have bugs.
There are always bizarre bugs in business development without understanding @Transactional failure scenarios or pit points.
It is also a high frequency test point when the interview!
This article lists the failure scenarios for @Transactional and analyzes why they fail.
Failure Scenario 1: The agent does not take effect
The nature of annotation parsing in Spring is proxy-based, and if the target method cannot be proxied by Spring, it cannot be transacted by Spring.
Spring generates proxies in two ways:
- Interface-based JDK dynamic proxies require the target proxy class to implement an interface to be proxied
- CGLIB proxy based on subclasses of the implementation target class
Prior to Spring 2.0, target classes used JDK dynamic proxies if they implemented interfaces, and CGLIB subclasses otherwise generated proxies.
After version 2.0, CGLIB is the default proxy generation method if the value specified in spring.aop.proxy-tartget-class is not shown in the configuration file, as shown below
Following the idea of an agent, let’s look at situations where transaction control fails because the agent does not take effect.
(1) Annotate interface methods with annotations
Transactional supports annotation on methods and classes. Once annotated on the interface, the CGLIB proxy for the implementation class of the interface will generate the proxy of the target class by subclassing it, which will not be resolved to @Transactional and the transaction will fail.
This is a mistake we make very rarely, and it is not officially recommended that we annotate the interface’s implementation class methods.
(2) classes or methods modified by the final or static keyword
CGLIB generates a proxy class by subclass of the target class. After final or static modification, it cannot inherit the methods of the parent class or its parent class.
(3) Internal invocation of class methods
Transaction management is implemented by proxy execution. If the method is called internally, the proxy logic will not be followed, so it will not be called.
For example,
The internal method createUser1 was called in createUser, and the transaction propagation policy for the createUser1 method was REQUIRES_NEW. The insert failed after the createUser1 method threw an exception.
But this kind of operation seems to be quite common in our business development process. How can we ensure its success?
Method 1: Create a new Service and migrate the method over, a bit muggle-like.
Method 2: Inject itself in the current class and call createUser1 with the injected userService
Method 3: Use aopContext.currentProxy () to obtain the proxy object
The idea is similar to approach 2, which is to access internal methods through proxies
(4) Current class is not managed by Spring
There is nothing to be said for this, neither is managed by Spring as a bean in the IOC container, let alone proxied by the transaction aspect.
This kind of Bug may seem silly, but someone might actually make a mistake.
Failure Scenario Set 2: Functions not supported by the framework or the underlying layer
Such failure scenarios focus on the internal support of the framework itself when parsing @Transactional. If the scenario used is not supported by the framework itself, the transaction will not take effect.
(1) Non-public modification methods
Make a breakpoint on any method marked Transactional and see the transaction cut point in idea as shown in the figure below
Click to go to this method, and there’s a call at the beginning
Continue to go in
You get this sentence
Methods that are not public decorators are not supported for transaction management.
(2) Multi-threaded call
The same with the above operations, we can step by step into the TransactionAspectSupport. PrepareTransactionInfo method.
Pay attention to the following statement
From this we know that transaction information is tied to the thread.
Therefore, in a multi-threaded environment, the transaction information is independent, leading to differences in how Spring takes over transactions.
We must pay special attention to this scene!
Let me give you an example
Main thread A calls thread B to save the data with Id 1. Then, main thread A waits for thread B to complete and queries the data with Id 1 through thread A.
You can’t find data with ID 1 in main thread A. Because these two threads are in different Spring transactions, they essentially exist in different Mysql transactions.
Mysql uses MVCC to ensure that the thread only reads data smaller than the current transaction number when the snapshot is read. In the case of thread B, the transaction number is obviously larger than thread A, so the data cannot be queried.
(3) The database itself does not support transactions
For example, Mysql’s Myisam storage engine does not support transactions, only InnoDB storage engine does.
This problem is extremely rare as innoDB storage engine is used by default after Mysql5.
However, if there is a configuration error or a historical project and you find that no transaction can be configured, remember to check whether the storage engine itself supports transactions.
(4) The transaction is not started
Question which is also a more muggles in Springboot project has gone, already have DataSourceTransactionManagerAutoConfiguration default open transaction management.
However, in an MVC project you also need to manually configure transaction parameters in the applicationContext.xml file. If you forget to configure, the transaction will definitely not take effect.
Transactional: Error using @transactional
Attention attention, the following several kinds of bugs will occur frequently!
(1) Wrong transmission mechanism
Spring supports seven propagation mechanisms, which are:
The above propagation mechanisms that do not support transactions are: PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER.
If these three propagation modes are configured, the transaction will not be rolled back in the event of an exception.
(2) The rollbackFor attribute is incorrectly set
By default, transactions roll back only run-time exceptions and errors, not checked exceptions (such as IOException).
So if an IO exception is thrown in a method, the transaction will also fail to roll back by default.
Full Exception catching can be done by specifying @transactional (rollbackFor = exception.class).
(3) Exceptions are caught internally
UserService
UserService1
The code above, UserService, calls the method in UserService1 and catches the exception thrown by UserService1.
You should see this error on the console:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
Copy the code
The transaction propagation mechanism for @Transactional annotated methods is REQUIRED by default. This feature supports the current transaction, i.e. joins the current transaction. We start the transaction in UserService and then throw an exception in UserService1 to roll back the transaction in UserService and mark it as read-only.
However, we caught an exception in UserSevice, at which point the transaction on UserService considered the transaction to have committed normally. The above exception is thrown if the transaction is found to be read-only and rolled back at commit time.
So if you need to catch a particular exception here, remember to throw it again so that the outermost transaction will feel it.
(4) Nested transactions
I want to roll back both UserService and UserService1. However, there may be scenarios where you just want to roll back the database operation reported in UserService1 without affecting the data drop in the main logic UserService.
There are two ways to implement this logic:
1. The entire method directly inside UserService1 is wrapped in a try/catch
2. Use the Propagation.REQUIRES_NEW mechanism in UserService1
Four,
This article examines 12 scenarios in which the @Transactional annotation fails during use
Finally, while the @Transactional annotation is nice, I recommend programmatic transactions for complex business logic to better manage transactions and manage the finer granularity of transactions when they are processed.
Contact me
If there is an incorrect place in the article, welcome to correct, writing the article is not easy, point a thumbs-up, yao yao da ~
WeChat: baiyan_lou
Public account: Uncle Baiyan