Sunday, July 20, 2008

Grails: Lessons Learned

Here are some lessons learned concerning lazy vs eager fetching and how to delete a child object in a One-to-many relationship (unfortunately it's not super obvious).

First Lesson: Don't set eager fetching globally
I by no means am a grails expert, but based on my experience don't set the eager fetching in your Domain as the example shows (unless you have a very good reason and understand the consequences). Lazy vs Eager fetching is well described in the grails documentation, so I won't repeat it, but anyone using One-to-many relationships needs to know the differences.

The default behavior in grails is lazy fetching, which results in n+1 queries. In some cases this might be ideal, in others it may not. When it's not you have a couple of choices. The example in the grails documentation sets a fetchMode property on the Domain. This sets it globally and every time the Domain is accessed, grails is going to load all it's many relationships. The path I recommend is to specify the fetch mode when retrieving the data. For example, the list() method has a parameter called fetch and can be used like this: Book.list(fetch: [authors: "eager"]). This gives you the most flexibility by not specifying the fetch mode globally, but allowing you to fetch eagerly when necessary.

Second Lesson: Use Hibernate Events to help remove associations
Like myself, you might actually have a One-to-many relationship where you need to delete a child. Unfortunately this use case isn't documented very well and it actually took me a little bit to figure out.

So lets say you have the following two domains
class Parent {
static hasMany = [kids: Kid]
String name
}

class Kid {
static belongsTo = [parent: Parent]
String name
}

And you save the following
new Parent(name: "James").addToKids(name: "Ayden").save()
Now Ayden turns 18 and going off to college and you need to remove him. You might think this would work:
Kid.findByName("Ayden").delete()
But it doesn't because the parent James still has a reference to the kid Ayden in the kids list (parent.kids). So you have to do the following:
def kid = Kid.findByName("Ayden")
kid.parent.removeFromKids(kid)
kid.delete()

Why that isn't the default behavior in grails I don't know, but to prevent you from repeating code everywhere you can use Hibernate events. In the Kid Domain add the following beforeDelete property:
class Kid {
static belongsTo = [parent: Parent]
def beforeDelete = {
parent.removeFromKids(this)
}
String name
}

And now when you want to remove a Kid, all you need to do is call kid.delete(). The hibernate events are interesting. By default grails supports 4 events: beforeInsert, beforeUpdate, beforeDelete, onLoad. However, there is a recent plugin called Hibernate Events Plugin that adds 7 more events: beforeLoad, afterLoad, beforeSave, afterSave, afterInsert, afterUpdate, afterDelete.

7 comments:

  1. On the eager fetching another thing to do would be to enable batch fetching or sub select fetching. Even if you forget to explicitly specify the fetch while querying hibernate will optimize the fetch (load associated objects / collections in batches rather one at a time), this would suffice for most cases.

    Hibernate fetch strategy

    For the parent child relationship it is probably better to modify the cascade attribute on the collection to all,orphan-delete. With this option you just need to remove the element from the parent collection and hibernate will delete the orphaned child object automatically.

    ReplyDelete
  2. I have a a one to many relationship where a parent has like thousands of kids, for which I have to do:

    new Parent(name: "James").addToKids(name: "kid1").
    .........
    addTokids(name, "kid2000").save()

    Is there a better way to do adding than this?. Any advices?

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. High grade assistance; I am going to undoubtedly come back to your website for extra know-how.
    Memphis Locksmith

    ReplyDelete