Sets, Lists & 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. The problem with this is there is no ordering when accessing the collection, which may not be what you want. To get custom ordering you can say that the set is a SortedSet:

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


In this case a java.util.SortedSet implementation is used which means you have to 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 collections of the Author class will be ordered by their release date.

Lists of objects (Since 0.5)

If you simply want to be able 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 in order to retain this order at the db level.

Note 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()

Maps of objects (Since 0.5)

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

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

def a = new Author() a.books = ["1590597583":"Grails 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.

2 Comments

  • Gravatar
    Implementing the "Comparable" interface just for sorting is not advised, since Set implementations also use the "compareTo" method, if available, to determine for equality.

    That means that in the book example on this page, if you add two books to a SortedSet that have the same releaseDate, only one of them will be contained by the set.

    It is better to use the injected .sort() method provided by Grails on collecitons, or to Collections.sort with a Comparator implementation, or to leverage the Grails 1.1 "sort" property that can be applied to a database mapping ( http://grails.org/doc/1.1/ref/Database%20Mapping/sort.html )

    Apr 08, 2009 14:04 PM tlars
  • Gravatar
    If you want to use the List behavior, then be aware that you can't use dynamic scaffolding, and need to do a little extra coding to support a bi-directional relationship. I found a good post describing the problem and solution here: http://kousenit.wordpress.com/2008/04/22/silly-gorm-tricks-part-i-lists/
    Jun 23, 2009 22:06 PM dbtanner

Post a Comment