7.2.1 Association in GORM - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.1.9
Table of Contents
7.2.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.7.2.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 {
}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]
}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()
Face:new Nose(face:new Face()).save() // will cause an error
Face instance, the Nose will go too:def f = Face.get(1) f.delete() // both Face and Nose deleted
hasOne property on the owning side, e.g. Face:Example C
class Face {
static hasOne = [nose:Nose]
}class Nose {
Face face
}nose table inside a column called face_id. Also, 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 Grails 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 }
}parent and supervisor properties are two directions of the same association. So when you set the parent property on a Person instance, Grails 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 Grails 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 }
}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.
7.2.1.2 One-to-many
A one-to-many relationship is when one class, exampleAuthor, has many instances of another class, example Book. With Grails you define such a relationship with the hasMany setting:class Author {
static hasMany = [books: Book] String name
}class Book {
String title
}The ORM DSL allows mapping unidirectional relationships using a foreign key association insteadGrails 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 Grails is "lazy", which means that the collection will be lazily initialized on first access. This can lead to the n+1 problem if you are not careful.If you need "eager" fetching you can use the ORM DSL or specify eager fetching as part of a queryThe 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
}mappedBy to specify which the collection is mapped:class Airport {
static hasMany = [flights: Flight]
static mappedBy = [flights: "departureAirport"]
}class Flight {
Airport departureAirport
Airport destinationAirport
}class Airport {
static hasMany = [outboundFlights: Flight, inboundFlights: Flight]
static mappedBy = [outboundFlights: "departureAirport",
inboundFlights: "destinationAirport"]
}class Flight {
Airport departureAirport
Airport destinationAirport
}7.2.1.3 Many-to-many
Grails supports many-to-many relationships by defining ahasMany 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
}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()
Book and not the authors!new Book(name:"Groovy in Action") .addToAuthors(new Author(name:"Dierk Koenig")) .addToAuthors(new Author(name:"Guillaume Laforge")) .save()
Grails' Scaffolding feature does not currently support many-to-many relationship and hence you must write the code to manage the relationship yourself
7.2.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 anicknames association that is a Set of String instances:class Person {
static hasMany = [nicknames: String]
}joinTable argument:class Person { static hasMany = [nicknames: String] static mapping = {
nicknames joinTable: [name: 'bunch_o_nicknames',
key: 'person_id',
column: 'nickname',
type: "text"]
}
}--------------------------------------------- | person_id | nickname | --------------------------------------------- | 1 | Fred | ---------------------------------------------
