After two days of debugging our codes on transaction handling, I’m writing this post to share my experience so others don’t have to undergo the same pain and torture. However, this post assumes that you’ve setup your Spring transactions – either via AOP or annotations. If you haven’t done so, here are some good references:
So, once you’ve setup or tried to setup transactions and failed – here are some tips how to debug your transaction management.
Enable logging. In log4j.properties, add log4j.logger.org.springframework.orm.jpa=DEBUG to enable logging of JPA transactions. This will allow you to trace the activities behind the Spring transaction processes. The logs provide detailed information about new, reused and active transactions, transaction modes, transaction commits and more. This is the best way to understand how transaction behaves on your application and gives you an insight how to fix your transactional problems.
Check your database and dialect. Dialects affect transaction handling so be sure to use the appropriate dialect for your database. Note that there are 2 dialects to define – JPA Dialect and Hibernate Dialect. JPA dialect is defined in the EntityManagerFactory while Hibernate is defined in Hibernate properties.
JPADialect is defined in EntityManagerFactory as shown below:
While Hibernate Dialect is defined via persistence.xml.
Watch out for listeners, they could mess up your transactions. A couple of examples about listeners (especially ApplicationListener) found online doesn’t consider transaction handling. Most of them performs data updates within the listener. This should not be the case because in doing so, you could be creating a transaction on the listener which forces all other operations to reuse that transaction. The better way of doing it is to call a service or business layer to perform transactional operations. This way, transaction is created only when the service is invoked.
Declare your transaction as readOnly whenever possible. Read-only transactions run faster so enable this on non-writing functions – (e.g. load, read, find, etc). Also, this avoid transaction commits on binded-data. One specific scenario is when Spring validation throws an error and data is still saved despite the validation error, this means that the data was loaded on a non-readOnly transaction and Spring transaction is committing it.
Reduce your transactional method calls. Put all your business logic function calls in one method marked with @Transactional and avoid calling other methods with their own transaction declarations. For example, if your transactions are declared on service layer, avoid calling another service method because this will trigger transactional checks which unnecessarily slows down the system. Call the DAO method directly instead. This way all your operation are contained in a single transaction declaration.
Understand propagation. Transaction propagation is hard to understand because the effects cannot be easily seen in your application… until you start performance tuning or having problems on saved data. Take note that if your transaction propagation is default, it will try to reuse existing transactions. This means that you could lose your transaction settings (e.g. readOnly), because new transaction was not created instead an existing was reused.