In this post I’ll create a placeholder that I’ll keep updating in the future accumulating Spring Transaction Management best practises that I find along the way:
Keep the @Transactional
definition at the Service layer and not the DAO layer. Service beans might use multiple DAOs ACID-ly under the same transaction. If otherwise transaction management is defined at the DAO level, Service beans will pay the price of creating multiple transactions for a conceptually grouped operation let alone the data inconsistencies we’ll risk having not defining ACID Service operations.
Further underlying the previous point we can define @Transactional(propagation = Propagation.MANDATORY)
at the class level of our DAOs, therefore enforcing the consumers of our DAOs to initiate transaction management.
Know what are the defaults of the @Transactional annotation and don’t just use it by faith. Namely, if not specified otherwise, propagation is set to Propagation.REQUIRED which means use an existing transaction otherwise create a new one; isolation is set to Isolation.DEFAULT
that is defined by the underlying DB default which normally results to Isolation.READ_COMMITED
; readOnly flag is switched off by default; rollbackFor can be defined for a Throwable class but beware: by default rollback occurs only if a RuntimeException is thrown unless this parameter is setup.
Be careful of the readOnly flag. Although @Transactional(readOnly = true, propagation=Propagation.REQUIRED)
will throw an exception upon a JDBC insert/update command within that transaction, it wouldn’t have the expected behavior on an insert/update ORM operation where the operation will unintuitively go through and be committed successfully. Under an ORM environment use the flag along with Propagation.SUPPORTS. In that case we won’t have to pay for the cost of creating a new transaction simply for a select operation unless there is one in place. Or even still consider ditching the whole @Transactional management for select operations.
Be careful when using Propagation.REQUIRES_NEW
that is not in the top level. It causes problems more times than not. Since every time a new transaction is wrapping that aspect, in cases where multiple REQUIRES_NEWs are included within the same transactional service method in cases of a rollback ACID is not respected and inconsistent data are left in the DB. On the other hand, just using it on the top level of the transaction method is fine and actually equates the default Propagation.REQUIRED
.