A Case Study: Dancing With the @Transactional Annotation in Exceptional Cases
A case study on using the @Transactional annotation in your Java code and how it can both help, and potentially hinder, your development process.
Join the DZone community and get the full member experience.Join For Free
@Transactional annotation is easy to use, but it may cause different types of errors if you don't know about the common pitfalls to look out for. In this article, I will show you the possible errors you can run into.
Suppose we write pseudocode like this: We have a service called DanceService.java and this service has a transactional method called
letsDance. This code runs perfectly. What happens when we have an exception?
letsDance method is called by a Controller or by a web service and proxy mode is used. So the proxy of the
DanceService class handles all the details of the transaction such as opening the connection, committing the transaction, and closing the connection.
According to the pseudocode above, what happens when we get an error in the
adjustMusic method? We want the business logic to continue running after receiving the exception. Inside this method, we call another service’s method: the
adjustVolume method and, in some cases, we throw an exception. How does the remaining code act when the exception is thrown by the
adjustVolume method? Remember the
adjustMusic method of
DanceService class and that we want to ignore all errors in the
adjustVolume method. Ok, let me show you the errors you will have. After the exception, the
oneStepLeft method runs and makes some updates in the database.
But in the first line of code in the
oneStepLeft method, which queries the database or updates the database, the line gets an error: “org.springframework.orm.jpa.JpaSystemException: could not inspect JDBC autocommit mode; nested exception is org.hibernate.exception.GenericJDBCException: could not inspect JDBC autocommit mode”. The pseudocode above has this error when calling
So why do we have an error? Didn’t they tell us that everything would be fine when we use the
When we carefully inspect the application server logs, we the following line of code before “could not inspect JDBC autocommit mode” error:
The transaction rolls back in the catch block of the
adjustMusic method when we get an exception, but it lets the code continue to run. When the first time code wants to access the database for doing some work, the code explodes.
And if we go further into the
letsDance method, we are now in the catch block. Then we jump to the
finally block and call the
createDanceLog method, which is transactional as well.
What happens when the line of code with
save() runs? Do we succeed or do we have an error?
Unfortunately, we get another error: “org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress.”
(I love this type of error very much. When I was new to JPA and got this error, I could not solve the problem and fix the error. There is not enough information and solution recommendations on the web.)
Here is the stacktrace:
Now, let me tell you the details about the source of this error: the transaction rolls back in the
adjustMusic method and then we continue. Because the transaction is broken down and ends before running the
createDeviceInfoLog method, the transaction is already marked for death. So when we try to get a connection for the database in the method and there is no transaction left, we get a “no transaction is in progress” error.
In this case, what should we do to fix the problem? After receiving an exception from
adjustVolume, the remaining the lines of code should run without getting another exception. When we get an exception from
MusicService, the transaction is rolled back.
Spring will automatically rollback the transaction if a runtime exception is thrown from a transactional method call.
To prevent automatic rollbacks, we should use the
noRollbackFor attribute of the
This annotation indicates that a rollback should not be issued if the target method raises this exception.
When we update the code above like this and then run the code again, there are no more “setRollbackOnly called on transaction” or “no transaction is in progress” exceptions, because the transaction is not rolled back and it commits perfectly in the end. The code as runs smoothly as we want.
This is a really weird case. When you get “no transaction is in progress error,” you probably say “oh my god, what did I do wrong?” We can solve our problem with the
noRollbackFor attribute, but before using this, we have tried many different code tricks and we have wasted our time and efforts. The solution is inside the details of Spring Declarative Transaction Management.
@Transaction annotation is a utility of Spring Declarative Transaction Management and it hides the complexity of dealing with transaction handling codes. However, it is not as easy as just putting the
@Transactional annotation in your code. You should understand the transaction management concept well before starting to code. Besides, you should carefully test your code for exceptional cases.
To sum up, you should be aware of the unexpected steps of your dance partner and you should adjust yourself quickly.
Opinions expressed by DZone contributors are their own.