(Quick Reference)

5 Domain Modelling in GORM

Version: 2023.3.0-SNAPSHOT

5 Domain Modelling in GORM

When building applications you have to consider the problem domain you are trying to solve. For example if you were building an Amazon-style bookstore you would be thinking about books, authors, customers and publishers to name a few.

These are modeled in GORM as Groovy classes, so a Book class may have a title, a release date, an ISBN number and so on. The next few sections show how to model the domain in GORM.

Consider the following domain class:

app/domain/org/bookstore/Book.groovy
package org.bookstore

class Book {
}

This class will map automatically to a table in the database called book (the same name as the class).

This behaviour is customizable through the ORM Domain Specific Language

Now that you have a domain class you can define its properties as Java types. For example:

package org.bookstore

class Book {
    String title
    Date releaseDate
    String ISBN
}

Each property is mapped to a column in the database, where the convention for column names is all lower case separated by underscores. For example releaseDate maps onto a column release_date. The SQL types are auto-detected from the Java types, but can be customized with Constraints or the ORM DSL.

5.1 Association in GORM

Relationships define how domain classes interact with each other. Unless specified explicitly at both ends, a relationship exists only in the direction it is defined.

5.1.1 Many-to-one and one-to-one

A many-to-one relationship is the simplest kind, and is defined with a property of the type of another domain class. Consider this example:

Example A

class Face {
    Nose nose
}
class Nose {
}

In this case we have a unidirectional many-to-one relationship from Face to Nose. To make this relationship bidirectional define the other side as follows (and see the section on controlling the ends of the association just below):

Example B

class Face {
    Nose nose
}
class Nose {
    static belongsTo = [face:Face]
}

In this case we use the belongsTo setting to say that Nose "belongs to" Face. The result of this is that we can create a Face, attach a Nose instance to it and when we save or delete the Face instance, GORM will save or delete the Nose. In other words, saves and deletes will cascade from Face to the associated Nose:

new Face(nose:new Nose()).save()

The example above will save both face and nose. Note that the inverse is not true and will result in an error due to a transient Face:

new Nose(face:new Face()).save() // will cause an error

Now if we delete the Face instance, the Nose will go too:

def f = Face.get(1)
f.delete() // both Face and Nose deleted

To make the relationship a true one-to-one, use the hasOne property on the owning side, e.g. Face:

Example C

class Face {
    static hasOne = [nose:Nose]
}
class Nose {
    Face face
}

Note that using this property puts the foreign key on the inverse table to the example A, so in this case the foreign key column is stored in the nose table inside a column called face_id.

hasOne only works with bidirectional relationships.

Finally, it’s a good idea to add a unique constraint on one side of the one-to-one relationship:

class Face {
    static hasOne = [nose:Nose]

    static constraints = {
        nose unique: true
    }
}
class Nose {
    Face face
}

Controlling the ends of the association

Occasionally you may find yourself with domain classes that have multiple properties of the same type. They may even be self-referential, i.e. the association property has the same type as the domain class it’s in. Such situations can cause problems because GORM may guess incorrectly the type of the association. Consider this simple class:

class Person {
    String name
    Person parent

    static belongsTo = [ supervisor: Person ]

    static constraints = { supervisor nullable: true }
}

As far as GORM is concerned, the parent and supervisor properties are two directions of the same association. So when you set the parent property on a Person instance, GORM will automatically set the supervisor property on the other Person instance. This may be what you want, but if you look at the class, what we in fact have are two unidirectional relationships.

To guide GORM to the correct mapping, you can tell it that a particular association is unidirectional through the mappedBy property:

class Person {
    String name
    Person parent

    static belongsTo = [ supervisor: Person ]

    static mappedBy = [ supervisor: "none", parent: "none" ]

    static constraints = { supervisor nullable: true }
}

You can also replace "none" with any property name of the target class. And of course this works for normal domain classes too, not just self-referential ones. Nor is the mappedBy property limited to many-to-one and one-to-one associations: it also works for one-to-many and many-to-many associations as you’ll see in the next section.

If you have a property called "none" on your domain class, this approach won’t work currently! The "none" property will be treated as the reverse direction of the association (or the "back reference"). Fortunately, "none" is not a common domain class property name.

Replacing a many-to-one collection

Given these GORM entities:

class Book {
    String name
    static hasMany = [reviews: Review]
}
class Review {
    String author
    String quote
    static belongsTo = [book: Book]
}

Imagine you have a book with two reviews:

new Book(name: 'Daemon')
                .addToReviews(new Review(quote: 'Daemon does for surfing the Web what Jaws did for swimming in the ocean.', author: 'Chicago Sun-Times'))
                .addToReviews(new Review(quote: 'Daemon is wet-yourself scary, tech-savvy, mind-blowing!', author: 'Paste Magazine'))
                .save()

You could create a method to replace the reviews collection as illustrated next:

Book replaceReviews(Serializable idParam, List<Review> newReviews) {
    Book book = Book.where { id == idParam }.join('reviews').get()
    clearReviews(book)
    newReviews.each { book.addToReviews(it) }
    book.save()
}

void clearReviews(Book book) {
    List<Serializable> ids = []
    book.reviews.collect().each {
        book.removeFromReviews(it)
        ids << it.id
    }
    Review.executeUpdate("delete Review r where r.id in :ids", [ids: ids])
}

Alternatively you could leverage cascade behaviour.

class Book {
    String name
    static hasMany = [reviews: Review]
    static mappping = {
        reviews cascade: 'all-delete-orphan'
    }
}
class Review {
    String author
    String quote
    static belongsTo = [book: Book]
}

The cascade behaviour takes cares of deleting every orphan Review. Thus, invoking .clear() suffices to remove the book’s previous reviews.

Book replaceReviews(Serializable idParam, List<Review> newReviews) {
    Book book = Book.where { id == idParam }.join('reviews').get()
    book.reviews.clear()
    newReviews.each { book.addToReviews(it) }
    book.save()
}

5.1.2 One-to-many

A one-to-many relationship is when one class, example Author, has many instances of another class, example Book. With GORM you define such a relationship with the hasMany setting:

class Author {
    static hasMany = [books: Book]

    String name
}
class Book {
    String title
}

In this case we have a unidirectional one-to-many. GORM will, by default, map this kind of relationship with a join table.

The ORM DSL allows mapping unidirectional relationships using a foreign key association instead

GORM will automatically inject a property of type java.util.Set into the domain class based on the hasMany setting. This can be used to iterate over the collection:

def a = Author.get(1)

for (book in a.books) {
    println book.title
}
The default fetch strategy used by GORM is "lazy", which means that the collection will be lazily initialized on first access. This can lead to the N+1 if you are not careful.

If you need "eager" fetching you can use the ORM DSL or specify eager fetching as part of a query

The default cascading behaviour is to cascade saves and updates, but not deletes unless a belongsTo is also specified:

class Author {
    static hasMany = [books: Book]

    String name
}
class Book {
    static belongsTo = [author: Author]
    String title
}

If you have two properties of the same type on the many side of a one-to-many you have to use mappedBy to specify which the collection is mapped:

class Airport {
    static hasMany = [flights: Flight]
    static mappedBy = [flights: "departureAirport"]
}
class Flight {
    Airport departureAirport
    Airport destinationAirport
}

This is also true if you have multiple collections that map to different properties on the many side:

class Airport {
    static hasMany = [outboundFlights: Flight, inboundFlights: Flight]
    static mappedBy = [outboundFlights: "departureAirport",
                       inboundFlights: "destinationAirport"]
}
class Flight {
    Airport departureAirport
    Airport destinationAirport
}

5.1.3 Many-to-many

GORM supports many-to-many relationships by defining a hasMany on both sides of the relationship and having a belongsTo on the owned side of the relationship:

class Book {
    static belongsTo = Author
    static hasMany = [authors:Author]
    String title
}
class Author {
    static hasMany = [books:Book]
    String name
}

GORM maps a many-to-many using a join table at the database level. The owning side of the relationship, in this case Author, takes responsibility for persisting the relationship and is the only side that can cascade saves across.

For example this will work and cascade saves:

new Author(name:"Stephen King")
        .addToBooks(new Book(title:"The Stand"))
        .addToBooks(new Book(title:"The Shining"))
        .save()

However this will only save the Book and not the authors!

new Book(name:"Groovy in Action")
        .addToAuthors(new Author(name:"Dierk Koenig"))
        .addToAuthors(new Author(name:"Guillaume Laforge"))
        .save()

This is the expected behaviour as, just like Hibernate, only one side of a many-to-many can take responsibility for managing the relationship.

5.1.4 Basic Collection Types

As well as associations between different domain classes, GORM also supports mapping of basic collection types. For example, the following class creates a nicknames association that is a Set of String instances:

class Person {
    static hasMany = [nicknames: String]
}

GORM will map an association like the above using a join table. You can alter various aspects of how the join table is mapped using the joinTable argument:

class Person {

    static hasMany = [nicknames: String]

    static mapping = {
       nicknames joinTable: [name: 'bunch_o_nicknames',
                           key: 'person_id',
                           column: 'nickname',
                           type: "text"]
    }
}

The example above will map to a table that looks like the following:

bunch_o_nicknames Table

---------------------------------------------
| person_id         |     nickname          |
---------------------------------------------
|   1               |      Fred             |
---------------------------------------------

5.2 Composition in GORM

As well as associations, GORM supports the notion of composition. In this case instead of mapping classes onto separate tables a class can be "embedded" within the current table. For example:

class Person {
    Address homeAddress
    Address workAddress
    static embedded = ['homeAddress', 'workAddress']
}

class Address {
    String number
    String code
}

The resulting mapping would looking like this:

5.2.2 composition
If you define the Address class in a separate Groovy file in the app/domain directory you will also get an address table. If you don’t want this to happen use Groovy’s ability to define multiple classes per file and include the Address class below the Person class in the app/domain/Person.groovy file. Another option is to define the Address class in src/main/groovy/Address.groovy and annotate it with grails.gorm.annotation.Entity

5.3 Inheritance in GORM

GORM supports inheritance both from abstract base classes and concrete persistent GORM entities. For example:

class Content {
     String author
}
class BlogEntry extends Content {
    URL url
}
class Book extends Content {
    String ISBN
}
class PodCast extends Content {
    byte[] audioStream
}

In the above example we have a parent Content class and then various child classes with more specific behaviour.

Considerations

At the database level GORM by default uses table-per-hierarchy mapping with a discriminator column called class so the parent class (Content) and its subclasses (BlogEntry, Book etc.), share the same table.

Table-per-hierarchy mapping has a down side in that you cannot have non-nullable properties with inheritance mapping. An alternative is to use table-per-subclass which can be enabled with the ORM DSL

However, excessive use of inheritance and table-per-subclass can result in poor query performance due to the use of outer join queries. In general our advice is if you’re going to use inheritance, don’t abuse it and don’t make your inheritance hierarchy too deep.

Polymorphic Queries

The upshot of inheritance is that you get the ability to polymorphically query. For example using the list() method on the Content super class will return all subclasses of Content:

def content = Content.list() // list all blog entries, books and podcasts
content = Content.findAllByAuthor('Joe Bloggs') // find all by author

def podCasts = PodCast.list() // list only podcasts

5.4 Sets, Lists and Maps

Sets of Objects

By default when you define a relationship with GORM it is a java.util.Set which is an unordered collection that cannot contain duplicates. In other words when you have:

class Author {
    static hasMany = [books: Book]
}

The books property that GORM injects is a java.util.Set. Sets guarantee uniqueness but not order, which may not be what you want. To have custom ordering you configure the Set as a SortedSet:

class Author {

    SortedSet books

    static hasMany = [books: Book]
}

In this case a java.util.SortedSet implementation is used which means you must implement java.lang.Comparable in your Book class:

class Book implements Comparable {

    String title
    Date releaseDate = new Date()

    int compareTo(obj) {
        releaseDate.compareTo(obj.releaseDate)
    }
}

The result of the above class is that the Book instances in the books collection of the Author class will be ordered by their release date.

Lists of Objects

To keep objects in the order which they were added and to be able to reference them by index like an array you can define your collection type as a List:

class Author {

    List books

    static hasMany = [books: Book]
}

In this case when you add new elements to the books collection the order is retained in a sequential list indexed from 0 so you can do:

author.books<<0>> // get the first book

The way this works at the database level is Hibernate creates a books_idx column where it saves the index of the elements in the collection to retain this order at the database level.

When using a List, elements must be added to the collection before being saved, otherwise Hibernate will throw an exception (org.hibernate.HibernateException: null index column for collection):

// This won't work!
def book = new Book(title: 'The Shining')
book.save()
author.addToBooks(book)

// Do it this way instead.
def book = new Book(title: 'Misery')
author.addToBooks(book)
author.save()

Bags of Objects

If ordering and uniqueness aren’t a concern (or if you manage these explicitly) then you can use the Hibernate Bag type to represent mapped collections.

The only change required for this is to define the collection type as a Collection:

class Author {

   Collection books

   static hasMany = [books: Book]
}

Since uniqueness and order aren’t managed by Hibernate, adding to or removing from collections mapped as a Bag don’t trigger a load of all existing instances from the database, so this approach will perform better and require less memory than using a Set or a List.

Maps of Objects

If you want a simple map of string/value pairs GORM can map this with the following:

class Author {
    Map books // map of ISBN:book names
}

def a = new Author()
a.books = ['1590597583':"My Book"]
a.save()

In this case the key and value of the map MUST be strings.

If you want a Map of objects then you can do this:

class Book {

    Map authors

    static hasMany = [authors: Author]
}

def a = new Author(name:"Stephen King")

def book = new Book()
book.authors = [stephen:a]
book.save()

The static hasMany property defines the type of the elements within the Map. The keys for the map must be strings.

A Note on Collection Types and Performance

The Java Set type doesn’t allow duplicates. To ensure uniqueness when adding an entry to a Set association Hibernate has to load the entire associations from the database. If you have a large numbers of entries in the association this can be costly in terms of performance.

The same behavior is required for List types, since Hibernate needs to load the entire association to maintain order. Therefore it is recommended that if you anticipate a large numbers of records in the association that you make the association bidirectional so that the link can be created on the inverse side. For example consider the following code:

def book = new Book(title:"New Grails Book")
def author = Author.get(1)
book.author = author
book.save()

In this example the association link is being created by the child (Book) and hence it is not necessary to manipulate the collection directly resulting in fewer queries and more efficient code. Given an Author with a large number of associated Book instances if you were to write code like the following you would see an impact on performance:

def book = new Book(title:"New Grails Book")
def author = Author.get(1)
author.addToBooks(book)
author.save()

You could also model the collection as a Hibernate Bag as described above.