For students working in Java development, the affairs of spring are certainly all too familiar. In some business scenarios, if there are multiple tables written at the same time, in order to ensure the atomicity of the operation (either succeed or fail at the same time) to avoid data inconsistencies, we generally use spring transactions.
Yes, spring transactions are mostly able to meet our business needs. But what I’m going to tell you today is that it has a lot of pitfalls, and if you don’t pay attention to the transaction, it will fail.
Believe it or not, let’s take a look.
1. Wrong access rights
We can see that the access to the add method is defined as private, which causes the transaction to fail, and spring requires that the surrogate method must be public.
There is a judgment in the computeTransactionAddSubmit method of the AbstractFallbackTransactionAttributeSource class that if the target method is not public, then TransactionAttribute returns null, that is, transactions are not supported.
2. The method is defined as final
We can see that the add method is defined as final, which causes the proxy object generated by spring app not to override the method, invalidating the transaction.
3. Method internal call
We see that in the transaction method add, the transaction method updateStatus is called directly. From the previous introduction, it can be seen that the updateStatus method has the ability to handle transactions because spring apo generates a proxy object, but this method directly calls the method of this object, so the updateStatus method does not generate a transaction.
4. The current entity is not managed by spring
We can see that the UserService class does not define @Service annotation, i.e. it is not handed over to spring to manage the bean instance, so its add method does not generate transactions.
5. Wrong spring transaction propagation characteristics
We can see that the transaction propagation characteristic of the add method is defined as Propagation.NEVER, which does not support transactions and throws exceptions if there is a transaction. Only these three propagation characteristics create a new transaction: PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, PROPAGATION_NESTED.
6. The database does not support transactions
The database engine prior to msql8 supported myslam and innerdb. I’ve also used it before, and for single-table operations that check more and write less, you might define the database engine of a table as myslam, which can improve query efficiency. However, it is important to remember that myslam only supports table locks, and does not support transactions. Therefore, write transactions to such tables are invalidated.
7. Swallow the abnormality yourself
In this case, the transaction is not rolled back because the developer caught the exception itself and did not throw it. The AOP of the transaction cannot catch the exception, resulting in the transaction not being rolled back even if an exception occurs.
8. The thrown exception is incorrect
In this case, the developer catches the exception himself and throws the exception: Exception, and the transaction is not rolled back. Because of spring transactions, only RuntimeException and Error are rolled back by default, not Exception.
9. Multi-threaded calls
We can see that in the transaction method add, the transaction method doOtherThing is called, but the transaction method doOtherThing is called in another thread, which will cause the two transaction methods to be not in the same thread, and the database connection obtained is different, so it is two different transactions. If you want to throw an exception in the doOtherThings method, it is impossible for the add method to roll back.
If you have seen the source code of spring transactions, you may know that spring transactions are implemented through a database connection. A map is saved in the current thread, key is the data source, and value is the database connection.
The same transaction we are talking about actually refers to the same database connection, and only having the same database connection can commit and roll back at the same time. If you are on a different thread, the database connection you get is definitely not the same, so it is a different transaction.
10. Nested transactions are rolled back a lot
This situation uses a nested internal transaction, which originally wanted to call the roleService.doOtherThings method, if there is an exception, only the content in the doOtherThings method is rolled back, and the content in userMapper.insertUser is not rolled back, that is, the savepoint is rolled back. But the truth is, insertUser also rolled back.
Because there is an exception in the doOtherThing method, it is not manually caught and will continue to be thrown upwards, and the exception is caught in the proxy method of the outer add method. Therefore, this situation rolls back the entire transaction directly, not just a single savepoint.
How can I just roll back savepoints?
Manually place internal nested transactions in the code in try/catch and do not continue to throw exceptions.
Introduced here, you will find that there are still a lot of pits in spring affairs
Likes and looks are the biggest support ❤️