Grails, gorm technologies

Association types in GORM

GORM (Grails Object Relational Mapping) is a plugin integrated with Grails and based on Java ORM - Hibernate. In this article, I'm going to describe how to make different kinds of associations in GORM.

One-to-many relationships

Let's assume there are two domain classes in your Grails application and you need to create a one-to-many relationship between these domains. For this purpose, you can use static hasMany property at the "one" side:

1
2
3
4
5
class User {
    static hasMany = [articles: Article]
}
class Article {
}

Two things happen here:

  • GORM creates a join table in the database:

    Unidirectional one-to-many

  • Grails automatically injects a Set of articles into the User domain (you can refer to it by the articles property).

This defines a cascade relationship between the two classes, that is cascading behaviour for saving and updating. For example:

1
2
3
4
def user = new User()
def article = new Article()
user.addToArticles(article)
user.save()

We've used dynamic addToArticles method to add created article to user. We have not saved the article explicitly, but it has been saved by cascade. In spite of cascading behaviour for saving and updating, this won't work for deleting. When you delete a user, only relationships between the user and articles are removed, but not the articles themselves (the records are only removed from the join table). There are two ways to delete articles by cascade:

  1. Use belongsTo in the Article domain:
    class User {
        static hasMany = [articles: Article]
    }
    class Article {
        static belongsTo = User
    }
    
  2. Configure cascading behaviour of the association using the following mapping:
    class User {
        static hasMany = [articles: Article]
        static mapping = {
            articles cascade: 'all-delete-orphan'
        }
    }
    class Article {
    }
    

In both cases, GORM creates a join table as well, but you do not have to worry about deleting user's articles, because they are deleted by cascade when you delete a user.

Relationship direction

Domain relationships can be either unidirectional or bidirectional. Have a look at the next example:

1
2
3
4
5
6
class User {
    static hasMany = [articles: Article]
}
class Article {
    static belongsTo = User
}

This is a unidirectional relationship: you can get user's articles via the articles set, but you cannot refer to the user from any of the articles. All the relationships in the examples above are unidirectional. You can add back references (i.e. properties linking back to the owner) in one of the following ways:

1
2
3
4
5
6
7
8
class Article {
    static belongsTo = [user: User]
}
// or: 
class Article {
    User user
    static belongsTo = User
}

Both of these examples describe a bidirectional relationship, so the user link can be used to refer to the owner from one of the articles. In this case, GORM does not create a join table. The bidirectional relationship is mapped through a straight foreign key on the article table:

Bidirectional one-to-many

A few examples of the referring to the User:

1
2
3
4
5
6
7
8
9
10
11
def user = new User()
def articles = [new Article(), new Article()]
articles.each { article ->
    user.addToArticles(article)
}
user.save()
assert Article.countByUser(user), 2 // countByUser method can't be executed without
                                    // the 'user' property in the Article domain
articles.each { article ->
    assert article.user.id, user.id // true for both of the articles
}

One-to-one

There are two ways of specifying a one-to-one relationship:

  1. Add an article property and the unique constraint to the User domain:
    class User {
        Article article
        static constraints = {
            article unique: true
        }
    }
    class Article {
        static belongsTo = [user: User]
    }
    
  2. Use the hasOne property on the owning (User) side:
    class User {
        static hasOne = [article: Article]
    }
    class Article {
        static belongsTo = [user: User]
    }
    

hasOne only works with bidirectional relationships. It means that you must have a belongsTo in the Article class with back reference to the User class. If it's missing, you'll get the following exception during project compilation:

org.hibernate.MappingException: hasOne property [User.article] is not bidirectional. Specify the other side of the relationship!

Note that the difference between those two cases is that GORM puts a foreign key to different tables:

  • to the user table in the first case: 1-to-1, the first case

  • to the article table in the second case: 1-to-1, the second case

Many-to-many

Many-to-many relationships in Grails can be specified by defining a hasMany property on both sides and having a belongsTo on the owned side:

1
2
3
4
5
6
7
class Tag {
    static belongsTo = Post
    static hasMany = [posts: Post]
}
class Post {
    static hasMany = [tags: Tag]
}

In this case, GORM will create the post_tags join table with the post_id and tag_id columns. These fields are foreign keys. GORM will perform cascade saving and deleting only if you save or delete an object of the Post domain class, because this is the relationship owner. The following example may clarify:

1
2
3
4
5
def post = new Post()
post.addToTags(new Tag())
post.addToTags(new Tag())
new Tag().addToPosts(post)
post.save()

Here, three new tags are saved in the database by cascade. If you want to delete a tag, you need to remove all its relationships with posts firstly. But when you delete a post, its relationships with tags are removed from the database automatically:

1
2
3
4
5
6
7
8
def tag = Tag.get(1)
def posts = [] + tag.posts ?: []
posts.each { Post post ->
    post.removeFromTags(tag)
}
tag.delete()

Post.get(1).delete()

Note: in this example, I have copied the tag's posts to a new collection to avoid the ConcurrentModificationException exception.

Another way to create a many-to-many relationship is manual join table mapping. All you need is:

  • create the PostTag domain class with fields of the Post and Tag types and a composite primary key
  • make the PostTag class serializable as it is a Hibernate composite primary key class
  • remove the hasMany and belongsTo from the Tag and Post domains
  • map the PostTag class to the existing post_tags table if you don't want to make any changes to the current database
1
2
3
4
5
6
7
8
9
10
class PostTag implements Serializable {

    Post post
    Tag tag

    static mapping = {
        id composite: ['tag', 'post']
        table 'post_tags'
    }
}

In this case, you'll need to save / delete PostTag objects explicitly. Note that if you'll try to delete some post or tag without deleting its relationships previously, you'll get the MySQLIntegrityConstraintViolationException exception and no object will be removed from the database:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`gormdb`.`posttags`, CONSTRAINT `FK74F452DAF7BD8628` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`))

In order to get the relationships from the join table, you can use any of the standard Grails dynamic methods on the PostTag domain (find, findAll, list).

Looking to hire a software developer?
Don't hesitate to contact us.

Comments