5.2.1.1 One-to-one
A one-to-one relationship is the simplest kind, and is defined trivially using 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 unidirectional many-to-one relationship from Face
to Nose
. To make it a true one-to-one you should make nose
unique:
class Face { Nose nose static constraints = { nose unique: true } } class Nose { }
To make this relationship bidirectional define the other side as follows:
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 and save it and the database updates/inserts will be cascaded down to 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
Another important implication of belongsTo
is that if you delete a Face
instance the Nose
will be deleted too:
def f = Face.get(1) f.delete() // both Face and Nose deleted
In the previous example the foreign key associated the Face
with the Nose
is stored in the parent as column called nose_id
. If you want the foreign key to be stored in the child you need a hasOne
association:
Example C
class Face {
static hasOne = [nose:Nose]
}
class Nose {
Face face
}
In this example you get a bidirectional one-to-one where the foreign key column is stored in the nose
table inside a column called face_id
.
5.2.1.2 One-to-many
A one-to-many relationship is when one class, example Author
, has many instances of a 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 }
In this case we have a unidirectional one-to-many. Grails 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
Grails 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)a.books.each { println it.title }
The default fetch strategy used by Grails is "lazy", which means that the collection will be lazily initialized. 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 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.2.1.3 Many-to-many
Grails 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 }
Grails 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 the below 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.