Domain Object Many-To-One relationship Grails GORM and SQL Relationship

This is following post series from ‘Domain Object Relationships in Grails GORM and SQL Relational Databases‘ where we look at the details how the relationships are build in GORM with how physically structrured in DB while cascading and direction addressed.

From Grails Documentation, it appears that the relationship One-To-One is the same as Many-to-One in Grails GORM. In this post, we look at One-To-One relationship used as Many-to-One one and whether it is true that One-to-One equals Many-To-One relationship?

In this post, we use the same example – Book<->Author as in the post on Domain Object One-to-One relationship. In fact, this post builds on it since we are not  going to cover db structure, coding or direction/cascading of the One-to-One relationship. We are building Many-To-One relationship with One-to-One relationship implementation:

 def authorToShare = Author.build().save(flush: true)
 def book1 = Book.build(author: authorToShare).save(flush: true)
 def book2 = Book.build(author: authorToShare).save(flush: true)

As you see, a single Author object – authorToShare is shared  by adding to two different Book objects – book1 & book2 turning the Book and Author  relationship from One-to-One to Many-to-One relationship

Many-To-One with Unidirectional One-To-One Relationship

First, we consider unidirectional One-to-One Book to Author relationship as described in the post ‘Domain Object One-to-One relationship Grails GORM and SQL Relationship‘ to accomplish Many-to-One book and author relationship. If we adding shared author  to two different book objects, it indirectly builds Many-to-One Relationship. To validate, lets have test:

def "one-to-one relationship between Book and Author"() {
 given:
 def authorToShare = Author.build().save(flush: true)

when:
 def book1 = Book.build(author: authorToShare).save(flush: true)
 def book2 = Book.build(author: authorToShare).save(flush: true)
 book1.refresh()

then:
 1 == Author.count()
 book1.author.id == book2.author.id

This test pass, so GORM permits adding more books(i.e. book1 & book2) with the same author(i.e.authorToShare) making it also Many-to-One relationship. From ER diagram of One-To-One relationship, it is possible to add more books that contains the same id for author(i.e. ‘author_id’ column) . In another words, in the ‘book’ table there is entry for each book that contains author_id column holding the same ‘id’ of Author.

Many-To-One with Bidirectional Relationship

The same test as above is run, however, the relationship between Book and Author this time is bidirectional instead of unidirectional one. When running test, the following exceptions is rised:

 More than one row with the given identifier was found: 1, for class: Book; nested exception is org.hibernate.HibernateException:

To better understand what is going on we update the test as following:

def "one-to-one relationship between Book and Author"() {
 given:
 def authorToShare = Author.build().save(flush: true)

when:
 def book1 = Book.build(author: authorToShare).save(flush: true)
 def book2 = Book.build(author: authorToShare).save(flush: true)
 book1.author = null
 book1.refresh()

then:
 1 == Author.count()
 book1.author.id == book2.author.id

By adding ‘book1.author=null'(highlighted line), the test passes. This is because, we adjust GORM session, so that there is no two objects(book1 & book2 in our case) pointing to the same object(authorToShare). Apparently, while it is fine with Database table structure (as we see with Unidirection relationship above), the GORM session mechanism prohibit a shared entity (i.e. author) to be reference more than one object (i.e. books). In short, with bidirectional One-to-One relationship, we are unable to turn it into Many-to-One relationship

To summarize, it is true that GORM One-To-One relationship is equal to Many-To-One but ONLY if Unidirectional One.  For Bidirectional One-to-One relationship, while db structure permits Many-to-One relationship, the GORM session it does not. Thus, if you need true one-to-one relationship, make sure it is bidirectional instead unidirectional. Otherwise, by accident its is possible to create Many-to-one that may cause strange behavior when deleting due to extra references present.

Leave a Reply

Your email address will not be published. Required fields are marked *