I have been updating all my services to be transactional by using Grail's ability to rollback when a RuntimeException is thrown in the service. I have, in most cases, doing this:
def domain = new Domain(field: field)
if (!domain.save()) {
throw new RuntimeException()
}
Anyways, I wanted to verify that this indeed will rollback the transaction... it got me thinking as to whether at this point it's already been committed.. Also, if not, would setting flush:true change that? I am not very familiar with how Spring/Hibernate does all of this :)
Rolls back an explicit or implicit transaction to the beginning of the transaction, or to a savepoint inside the transaction. You can use ROLLBACK TRANSACTION to erase all data modifications made from the start of the transaction or to a savepoint.
javax.transaction RollbackException exception is thrown when the transaction has been marked for rollback only or the transaction has been rolled back instead of committed. This is a local exception thrown by methods in the UserTransaction , Transaction , and TransactionManager interfaces.
To enable transactions on a service use the Transactional transform: import grails.gorm.transactions.* @Transactional class CountryService { } The result is that all methods are wrapped in a transaction and automatic rollback occurs if a method throws an exception (both Checked or Runtime exceptions) or an Error.
Yep that'll do it.
Transactions in Grails are by default handled at a Service method level. If the method returns normally then the transaction will be committed, if a RuntimeException is thrown the transaction will be rolled back.
Note this means even if you use flush:true on while saving an object in the server method the db changes will still be rolled back if you throw a RuntimeException.
For example:
class MyService {
def fiddle(id,id2){
def domain = Domain.findById(id)
domain.stuff = "A change"
domain.save( flush:true ) // will cause hibernate to perform the update statements
def otherDomain = OtherDomain.findById(id2)
otherDomain.name = "Fiddled"
if( !otherDomain.save( flush:true )){ // will also write to the db
// the transaction will be roled back
throw new RuntimeException("Panic what the hell happened")
}
}
}
What I'm not 100% clear on with Grails is what happens if a checked exception is thrown in straight java/spring world the default behaviour is for the transaction inceptor to commit the transaction, althrough this can be overriden in the config.
Note: there is a caveat, and that is that your db has to support transactions on the tables you are updating. Yes, this is poke at MySQL :)
This also applies to the Domain.withTransaction
method.
Just wanted to add additional comments to the accepted answer, and this was too long to fit into a comment.
What I'm not 100% clear on with Grails is what happens if a checked exception is thrown
By default, the exception must NOT be checked, or the transaction won't be rolled back. Apparently that's a Spring thing.
If you really want to check the exceptions on a method, you can explicitly mark the service method as @Transactional
and use the rollbackFor
argument to list which exceptions should still cause a rollback. (Note that I haven't actually tested this.)
Be aware, though, that marking any one method in a service with @Transactional
disables the automatic wrapping of its other methods with a transaction. So, if you do it for one, you have to do it for all of them. Be sure you really need to declare those checked exceptions ;)
You can read more about this at http://docs.grails.org/latest/guide/services.html.
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