Using transactional behaviour with Gorm
All communication between Hibernate and the database runs within the context of a database transaction but the Session itself is lazy in that it only ever initiates a database transaction at the last possible moment.Given that there is a transaction, you would think that if something went wrong, any problems would be rolled back. However, without specific transaction boundaries and if the Session is flushed, any changes are permanently committed to the database.
This is a particular problem if the flush is beyond your control for instance, the result of a query. Then those changes will be permanently persisted to the database. This is a particular problem if the flush is beyond your control for instance, the result of a query. Then those changes will be permanently persisted to the database which may create problems.
For example
[java]
def save = {
def album = Album.get(params.id)
album.title = "Changed Title"
album.save(flush:true)
…
// something goes wrong
throw new Exception("Oh, sugar.")
}
[/java]
Now when some exception occurs, the database has already been updated, you cannot roll back at this stage.This problem can be solved by either placing code in service or adding transactional behaviour to GORM
[java]
def save = {
Album.withTransaction {
def album = Album.get(params.id)
album.title = "Changed Title"
album.save(flush:true)
…
// something goes wrong
throw new Exception("Oh, sugar.")
}
}
[/java]
If an exception is thrown, all changes made within the scope of the transaction will be rolled back as expected.
Grails uses Spring’s PlatformTransactionManager abstraction layer under the covers. In this case, if an exception is thrown, all changes made within the scope of the transaction will be rolled back as expected. The first argument to the withTransaction method is a Spring TransactionStatus object, which also allows you to programmatically roll back the transaction by calling the setRollbackOnly() method.
[java]
def save = {
Album.withTransaction { status ->
def album = Album.get(params.id)
album.title = "Changed Title"
album.save(flush:true)
…
// something goes wrong
if(hasSomethingGoneWrong()) {
status.setRollbackOnly()
}
}
}
[/java]
Very nicely written by the author!
Please change “throw new Exception” to “throw new RuntimeException” or some other subclass of RuntimeException (you shouldn’t throw plain Exception or RuntimeException) or an Error.
Although Groovy doesn’t force you to catch checked exceptions, that doesn’t change the way transactions work in Spring. Checked exceptions do _not_ automatically roll back transactions since in Java you have to catch them or add them to the throws declaration. Only unhandled runtime exceptions and errors automatically rollback transactions since it’s assumed that you didn’t use a try/catch.