I'm experimenting with using the command pattern to allow my web layer to work with Hibernate entities within the context of a single transaction (thus avoiding lazy loading exceptions). I am, however, confused now with how I should deal with transactions.
My commands call service layer methods that are annotated with @Transactional
annotations. Some of these service layer methods are read-only – e.g. @Transactional(readOnly = true)
– and some are read/write.
My service layer exposes a command handler that executes commands passed to it on behalf of the web layer.
@Transactional
public Command handle(Command cmd) throws CommandException
I assume I am right in making the command handler's handle
method transactional. This is where the confusion comes in. If the implementation of a command makes calls to multiple service layer methods, there is no way for the command handler to know whether operations called within the command will be read-only, read/write or a combination of the two.
I don't understand how propagation works in this example. If I were to make the handle()
method readOnly = true
, then what happens if the command then calls a service layer method that is annotated with @Transactional(realOnly = false)
?
Best Answer
First of all, since Spring doesn't do persistence itself, it cannot specify what
readOnly
should exactly mean. This attribute is only a hint to the provider, the behavior depends on, in this case, Hibernate.If you specify
readOnly
astrue
, the flush mode will be set asFlushMode.NEVER
in the current Hibernate Session preventing the session from committing the transaction.Furthermore, setReadOnly(true) will be called on the JDBC Connection, which is also a hint to the underlying database. If your database supports it (most likely it does), this has basically the same effect as
FlushMode.NEVER
, but it's stronger since you cannot even flush manually.Now let's see how transaction propagation works.
If you don't explicitly set
readOnly
totrue
, you will have read/write transactions. Depending on the transaction attributes (likeREQUIRES_NEW
), sometimes your transaction is suspended at some point, a new one is started and eventually committed, and after that the first transaction is resumed.OK, we're almost there. Let's see what brings
readOnly
into this scenario.If a method in a read/write transaction calls a method that requires a readOnly transaction, the first one should be suspended, because otherwise a flush/commit would happen at the end of the second method.
Conversely, if you call a method from within a readOnly transaction that requires read/write, again, the first one will be suspended, since it cannot be flushed/committed, and the second method needs that.
In the readOnly-to-readOnly, and the read/write-to-read/write cases the outer transaction doesn't need to be suspended (unless you specify propagation otherwise, obviously).