Many-to-many relationship mapping with GORM/Grails

I’ve been working on a pet project in my spare time – a sort of twitter clone that authenticates against Atlassian Crowd. I’m using the awesome Grails/Crowd plugin, which is pretty seamless (but needs a bit of internal polishing).

I’ve only scratched the surface of Grails, but here are a few things I’ve run into as a Grails newb.

Many-to-many relationships and cascading updates

GORM (Grails’s abstraction of Hibernate) lets you define a many-to-many relationship like this:

class User {
    String name
    static hasMany = [groups: Group]
}
 
class Group {
    String name
    static hasMany = [members : User]
    static belongsTo = User 
}

This relationship can be accessed through some helpful dynamic methods that Grails provides:

new User(name: "foo").addToGroups(name: "bar").save()
// creates a new user AND group, saves both of them 
// and the relationship 
 
def user = new User(name: "foo").save()
new Group(name: "bar").save().addToMembers(user)
// as above, but addToMembers will not cascade the relationship 
// to the user

The crucial part here is the belongsTo property of the domain classes. This tells GORM which class should “own” the relationship. What this means is that if you want the relationship to cascade through both objects, you will need to call the addTo* method on the owning object.

Many-to-many relationships with the same class

Back to the pet project – one of the features I really wanted was the ability to subscribe to status updates for a whole group.

Here’s a quick ERD (generated by the excellent IDEA/Grails integration) of the domain classes:

You can quickly see that there are two many-to-many relationships between the classes User and Group.

The code looks something like this now (other stuff stripped out):

class User {
    String name
    static hasMany = [groups: Group, groupSubscriptions: Group]
    static mappedBy = [groups: "members"]
}
 
class Group {
    String name
    static hasMany = [members : User]
    static belongsTo = User 
    static mapping = {
        // we can't have a table called group 
        table "group_" 
    }
}

I thought that Grails would be smart enough to see what I was doing, but I was wrong (for various reasons). To cut to the chase, here’s the necessary code to implement this:

class User {
    String name
    static hasMany = [groups: Group, groupSubscriptions: Group]
    static mappedBy = [groups: "members",
                       groupSubscriptions: "subscribers"]
 
    static mapping = {
        groupSubscriptions joinTable: "user_group_s" 
    }
}
 
class Group {
    String name
    static hasMany = [members : User, subscribers: User]
    static belongsTo = User 
 
    static mapping = {
        // we can't have a table called group 
        table "group_" 
        subscribers joinTable: "user_group_s" 
    }
}

Grails will create two relationship tables user_group_ and user_group_s. Having the back reference from Group to User isn’t so bad, since the collection is lazy loaded. I would have just preferred not to pollute the domain class.

Conclusion

I hope that someone out there finds this information useful, I’d hate to see another Grails newb fall into this big dark pit that I call lack of documentation.

That said, my experience with Grails and Groovy so far has been pretty awesome. If you’re looking to develop a web-app rapidly, I’d definitely be looking at Grails as an option.

6 comments

  1. Avatar

    By John Adelus 8 months later:


    Your section on “Many-to-many relationships and cascading updates” definitely gave me an Ah-ha moment! Thanks.

  2. Avatar

    By Ling 9 months later:


    Thanks for your example. I’m facing some problem with the Many-to-Many relationship.

    Below is my domain class: class Action{

    String name
    Action parentNode
    static belongsTo = Action
     
    static hasMany = [nextList:Action, failList:Action, passList:Action]    
    

    }

    How the mapping and mappedBy work for the domain class above.

    Please help! Thanks alot!!

  3. Avatar

    By Chris Broadfoot 9 months later:


    Ling,

    I’m not sure. I’ve never done multiple many-to-many relationships with the same class.

    I suspect you’ll need something like this:

    class Action {
        String name
        Action parentNode
     
        static belongsTo = Action
        static hasMany = [nextList:Action, failList:Action, passList:Action]
     
        static mappedBy = [ nextList: "action_nextlist",
                            failList: "action_faillist",
                            passList: "action_passlist" ]
     
        static mapping = {
            nextList joinTable: "action_nextlist"
            failList joinTable: "action_faillist"
            passList joinTable: "action_passlist"
        }
    }
    
  4. Avatar

    By Giancarlo Frison 10 months later:


    Thanks for the clue!

  5. Avatar

    By olga over 1 year later:


    useful, very clearly written post for the grails newbies! if you add examples how to retrieve instances from many-to-many relationships using grails options, the post would be complete.

  6. Avatar

    By David about 2 years later:


    Hi Chris, I put this to use in my own project and it worked right-away! I am also a Grails newb and after a couple of days of Grails doco and Googling I came up empty until I found your post. A much needed relief from guff that I get as a Grails newb whenever I raise my hand on the Grails IRC chat channel: #grails. On the channel I spend most of my time typing:

    /ignore

    :-David