Friday, July 24, 2009

Grails Transactions In Brief

OK so first ever blog post, lets keep things short!

I was playing around with transactions in Grails as part of a project a while ago, bearing in mind I have no Spring experience, I was a little worried it might be complicated. Need I worry??? Lord no! Grails to the rescue once again, however there is one 'Gotcha' that I feel is worth putting out there, hence me writing this 'blog'.

Basically transactions are a facet of Grails (obtained from Spring I think) that allow us to roll back database writes if an exception occurs mid-method making our databases cleaner than.... hmm.. don't want to write an offensive metaphor in my first post so lets leave it at clean :)

Suppose we have the following method in our service:

static transactional = true

def serviceMethod(params1,params2){

def domain1 = new Domain()
domain1.save()

def domain2 = new Domain()
domain2.save()//Exception thrown here
}




So because this is transactional, when the exception is thrown while saving domain2, domain1 should be deleted from the database (well that was my assumption). However this is NOT the case. After some reading, it seems that the roll back will only occur if the exception thrown is of type RuntimeException. This means that we would have to catch an exception and then throw a RuntimeException, which I think makes this sort of transactional behavior pointless... if we know where exceptions are going to occur we could just roll back ourselves maybe.... I'm sure there's a reason :)

However, all is not lost!! We can take finer grained control of transactions using the Domain.withTransaction{} closure where Domain is the name of a domain in our project. To use this, we must set the service's transactional property to false. Here's how it works:


static transactional = false

def serviceMethod(params1,params2){

Domain.withTransaction{tx ->

def domain1 = new Domain()
domain1.save()

def domain2 = new Domain()
domain2.save()//Exception thrown here

}

}


Now when any exception is thrown at domain2.save() (probably before it), the transaction is rolled back and domain one is deleted. We can also roll back programmically by using tx.setRollbackOnly(). Actually saying domain one is deleted probably isn't correct because they are never persisted, just saved to the session... i think!

Anyways, I hope this helps some people new to dealing with transactions in Grails. Feedback is welcome and also if anyone knows how to make my source code indent properly, by all means share :)

John