My Grails service is having an issue where a swallowed exception unrelated to a transaction is causing the transaction to rollback even when it is unrelated to the persistance of the domain object.
In my service I have something along the lines of
updateSomething(domainObj) {
def oldFilename = domainObj.filename
def newFilename = getNewFilename()
domainObj.filename = newFilename
domainObj.save(flush: true)
try {
cleanUpOldFile(oldFilename)
} catch (cleanupException) {
// oh well, log and swallow
}
}
What I am seeing is that when I have exception when I am cleaning up the old file, I log it and swallow it, but it still causes the transaction to rollback, even though I am already done updating the domain object.
How do I limit the scope transaction to complete before the clean up or is there another way to get the clean up exception to not cause a rollback?
Just for the record I am using Grails 2.1.1
Usually, you use a rollback exception strategy to handle errors that occur in a flow that involve a transaction. If the transaction fails, that is, if a message throws an exception while being processed, then the rollback exception strategy rolls back the transaction in the flow.
Note that by default, rollback happens for runtime, unchecked exceptions only. The checked exception does not trigger a rollback of the transaction.
Note however that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.)
@Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database, making the system inconsistent.
You can use annotations to do more fine-grained transaction demarcation. By default services are transactional, and all public methods are transactional. But if you use any @Transactional
annotations, Grails doesn't make everything transactional - you have complete control.
Runtime exceptions automatically trigger rollbacks, but checked exceptions don't. Even though Groovy doesn't required that you catch checked exceptions, the feature is a Spring thing which doesn't know about Groovy exception handling.
Transactions are implemented by wrapping your service class instance in a proxy. If an exception "escapes" the proxy, whether it's then caught or not, the rollback will have already happened.
So you have a few options. Annotate updateSomething
as @Transactional
but don't annotate cleanUpOldFile
:
import org.springframework.transaction.annotation.Transactional
@Transactional
def updateSomething(domainObj) {
...
}
def cleanUpOldFile(...) {
...
}
You can also annotate cleanUpOldFile with one or more unchecked exceptions that shouldn't roll back a transaction (or in other use cases checked exceptions that should), e.g.
@Transactional(noRollbackFor=[FooException, BarException])
def cleanUpOldFile(...) {
...
}
In addition to @Burt Beckwith's answer, if you have a service where you just don't want transactions (which I actually did in my case) you can turn off transactions on all public methods by adding
static transactional = false
to the Service class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With