Unidirectional many-to-many relationships in Grails, and how it could affect the batch performance decisively.

von Xuetao Li

Mapping business domains using GORM (Grails Object Relational Mapping) is in most cases straightforward and uncomplicated to implement.
The documented way to map many-to-many relationships in Grails is to define a hasMany on both sides of the relationship AND having a belongsTo on the owned side: [1]

class Fee {
    static hasMany = [foos:Foo]
    ...
}
class Foo {
    static hasMany = [fees:Fee]
    static belongsTo = Fee
    ...
}

The example given above demonstrates a bidirectional many-to-many association between Fee and Foo. Both sides of the relationship are holding references to the other side. This might be the most common use case.

What if you don't really need references from both sides, but only from one side (unidirectional)? Someone could ask - "Does it make sense to have unidirectional many-to-many? The references are per default lazy loaded and therefore seems to be harmless." - Not in a particular situation.

In a project that is in production since years we have batch jobs for persisting a large number of domain entities into the database. Thanks to the approaches introduced by Ted Naleid [2] the performance of the batches was fine. We are using the service method cleanUpGorm() at regular intervals to release memory usage by the hibernate session:

def cleanUpGorm() {
    def session = sessionFactory.currentSession
    session.flush()
    session.clear()
    propertyInstanceMap.get().clear()
}

Consider the following situation: 

  • There is a many-to-many relationship between Fee and Foo
  • A Foo instance, let's say "foo-1" is referenced by 200,000 Fee instances
  • Batch has inserted some more Fee instances which are also referenced to "foo-1"

In this case, the call to session.clear() was taking minutes after the batch process has inserted 30 Fee instances. With such a performance the batch won't be finished within the time window. We used to have a much better performance and the problem must be fixed for the total changes on that branch to be released.

 
One of the solutions here is to have a unidirectional many-to-many relationship from Fee to Foo. The result in the giving situation was a performance boost up to 10x faster. The following example shows how to achieve it with GORM:
class Fee {
    static hasMany = [foos:Foo]
    ...
}
class Foo {
    static hasMany = [Fee]
    ...
}

Fee is the owning side and is responsable for persisting relationships. Foo is the owned side and do not have any references to Fee.

Conclusion

Unidirectional many-to-many relationships is supported by hibernate [3] and Grails.
The GORM keyword belongsTo is mandatory for bidirectional, but not for unidirectional many-to-many relationships.
To achieve a unidirectional many-to-many you should define a "hasMany" without references and without belongsTo on the owned side.

Kategorien: GORMGrails

Zurück