Wednesday, December 3, 2008

Stealth little gotcha in grails (with a happy ending of course)

So i noticed a little Gotcha in Grails the other day. This is all down to the way hibernate works and and how grails uses it.

The way hibernate works:

When you load objects using hibernate this is done using an hibernate session. Once an object is loaded by the hibernate session it automatically becomes a managed object as far as hibernate is concerned. By managed, amongst other things i mean to automatically do dirty checking on any changes to the object.

Now when hibernate is setup for auto flushing (as is the default case with grails) it will flush any changes to the object to the database on the following events:

  • Whenever a query is run
  • Directly before a transaction is committed
  • Directly after a grails action completes if no exception is thrown

So this leads to some interesting Gotchas if you are not careful especially in the update action:

So here is an example:

Say i have the following domain object:

class EBook{
String name;
String summary;
String filename;

static constraints = {
name(blank:false)
summary(nullable:true)
}

}


Now in my controller i have this:

def update = {
def ebook = Ebook.get(params.id)
if (ebook) {
ebook.properties = params;

if(!ebook.hasErrors() && ebook.validate()){
if(!ebook.fileName){
ebook.generateFileName() //generates a default filename based on the name
}
if(ebook.save()){
flash.message = "Your changes have been saved"
render(view: 'show', model: [ebook: ebook])
}else{
render(view: 'edit', model: [ebook: ebook])
}

}else{
render(view: 'edit', model: [ebook: ebook])
}

}else{
render(view: 'edit', model: [ebook: ebook])
}

}


Now the problem with this code is. If validation fails then the ebook object still gets saved to the database. This is because of the rules discussed above. The ebook object has been changed via ebook.properties = params so hibernate knows it is dirty. So when the action finishes hibernate will flush the dirty changes to the database. This means i get bad data in my database.

So what to do ? Well like everything in Grails it turns out to be VERY easy to solve. The discard() method to the rescue.

def update = {
def ebook = Ebook.get(params.id)
if (ebook) {
ebook.properties = params;

if(!ebook.hasErrors() && ebook.validate()){
if(!ebook.fileName){
ebook.generateFileName() //generates a default filename based on the name
}
if(ebook.save()){
flash.message = "Your changes have been saved"
render(view: 'show', model: [ebook: ebook])
}else{
render(view: 'edit', model: [ebook: ebook])
}

}else{
ebook.discard()
render(view: 'edit', model: [ebook: ebook])
}

}else{
render(view: 'edit', model: [ebook: ebook])
}

}


The discard() method will detatch the ebook object from the session. This should then solve the problem. The only possible issue then would be LazyInitializationException depending on your object. Anyway for now that is a solution for my problem but may not a solution for all problems like this.

Hope this helps :)