In this post we’ll dig into some problems that can arise when using nested transactions in methods that are under test in integration test. I’ll also talk about when nested transactions might be needed.
Paint me a picture Ron
Dateline, early February 2016 - our hero is diligently trying to find a problem with the company’s content engine that’s causing reports to go missing and lengthy processes to die at the last minute.
The logging produced is scant, but indicates that the transaction for this work has been marked as ‘rollback only’, and once the outermost service method exits the transaction bails, undoing all of the database updates from the previous hour’s worth of work.
Digging in, no culprit can be easily identified, so it’s decided that the small units of work that make up the larger whole should themselves be wrapped in nested transactions - that way, no single piece of content can cause a rollback of the entire process.
The frustrating thing about this issue is that the output of the process, a set of files, was actually being produced without issue. Unfortunately, the link to where those items reside in our long term storage was supposed to be saved as part of the task representation - in our relational database - which meant the links where removed after the rollback! To the poor user it seemed the work would make forward progress, completion status updating all the way, but then at the very end bomb-out with a generic error message and no links to the expected files!
Hibernate exposes a means to use nested transactions. By doing this, any possible problem that occurs within the nested transaction will trigger a rollback of that inner transaction only! This is exactly the solution we needed, allowing for more a thorough investigation after unblocking our users.
NESTED vs REQUIRES_NEW
There’s a propagation behavior in Spring named
REQUIRES_NEW - this behavior is different in a crucial
NESTED. When running a nested transaction, it will not be commited until the outer transaction
completes. A new (from
REQUIRES_NEW) transaction will commit on completion and may not have the same
visibility into pending transactions of the outer transaction as a nested one would.
This StackOverflow answer describes it as well.
Now, let’s look at exactly what is involved with getting some nested transactions into Grails.
Creating nested transactions in Grails
The first thing to do is mark out the space where the nested transaction should occupy. If it’s at all possible in your situation make the boundary at a service interaction layer. In Grails, the Spring Interceptor sits between services and provides transactional behavior based on annotations (or the application defaults if not specified through annotations). Let’s see what that might look like:
The service above,
NestedTransactionService, will run its
businessLogic method inside a nested inner
transaction of the caller’s transaction. The use of the Spring annotation
@Transactional allows us to
specify the propagation behavior,
and timeout. See the full documentation
for more information.
A word of warning
As mentioned above, the Spring Interceptor sits between service. This means that intra-service method calls annotated with transaction metadata will be called without honoring the transaction configuration.
Here’s an example
So what if we can’t insert a service boundary?
If you are refactoring existing code or for other reasons can’t break the dangerous logic
out into a separate service, you may be wondering how to proceed. We can’t just extract it
into a method, annotate it with
@Transactional, and move on with life because of the Spring
Interceptor behavior. Luckily, Spring offers a means to do this with code - a means that also
includes greater flexibility.
Introducing the TransactionTemplate
provides ‘programmatic transaction demarcation’. It provides an
execute method taking a
(called synchronously). The signature for this method is:
Object will be whatever is returned from
null if action doesn’t return
anything. What makes the
TransactionTemplate of interested, beyond this
execute method, is the
configurability it provides. A new
TransactionTemplate can be constructed with a
which collects all of the items provided by the
@Transactional annotation (timeout, propagation behavior,
isolation level, etc) - or - configured post-construction using bean style mutators (
actually implements the
TransactionDefinition interface). This will prove crucial when we get to the
test interaction piece.
Let’s look at a
Now we have wrapped the call to
businessLogic in a transaction boundary. How do we know this is
using a nested transaction and not a new or existing one? By configuring the
txTemplate in the
Grails bean definition file -
Let’s take a look:
One final but important note
By default the
transactionManager bean provided by Grails does not support nested transactions. You’ll
know that you missed this step when you see error messages about ‘nested transactions are not supported’.
Luckily this is an easy step - in your
BootStrap.groovy (or similar application startup logic) just
add the following:
See the full documentation for more info.
Ron, this is great! … WAIT MY INTEGRATION TESTS ARE BREAKING!!!
Fear not readers - a solution exists!
Integration test cases in Grails run within a helpfully provided transaction. This transaction ensures that database changes in one test case don’t leak into subsequent test cases. For some reason (hit me up on Twitter and tell me why) use of nested transactions in this manner causes the outer transactions to be commited and leak over into integration tests. In our codebase, this manifests as uniqueness constraint violations when creating test fixtures.
Using the support for
Environment specific configuration in Grails, we will use a different
TransactionTemplate in our service. Here’s an updated
resources.groovy showing what I mean:
With the call to
Environment.executeForCurrentEnvironment we inform grails that for the
test environment, a non-nesting
TransactionTemplate should be used.
Until next time friends!