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 toPropagation.REQUIRED
which means use an existing transaction otherwise create a new one; isolation is set toIsolation.DEFAULT
that is defined by the underlying DB default which normally results toIsolation.READ_COMMITED
;readOnly
flag is switched off by default;rollbackFor
can be defined for aThrowable
class but beware: by default rollback occurs only if aRuntimeException
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 withPropagation.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 multipleREQUIRES_NEW
s are included within the same transactional service method in cases of a rollbackACID
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 defaultPropagation.REQUIRED