Lazy Collections
As discussed in the section on
Eager and Lazy fetching, by default GORM collections use lazy fetching and is is configurable through the
fetchMode
setting. However, if you prefer to group all your mappings together inside the
mappings
block you can also use the ORM DSL to configure fetching:
class Person {
String firstName
static hasMany = [addresses:Address]
static mapping = {
addresses lazy:false
}
}
class Address {
String street
String postCode
}
Lazy Single-Ended Associations
In GORM, one-to-one and many-to-one associations are by default lazy. Non-lazy single ended associations can be problematic in cases when you are loading many entities which have an association to another entity as a new SELECT statement is executed for each loaded entity.
You can make one-to-one and many-to-one associations non-lazy using the same technique as for lazy collections:
class Person {
String firstName
static belongsTo = [address:Address]
static mapping = {
address lazy:false
}
}
class Address {
String street
String postCode
}
Here we set the
address
property of the
Person
class will be eagerly fetched.
Lazy Single-Ended Associations and Proxies
In order to facilitate single-ended lazy associations Hibernate uses runtime generated proxies. The way this works is that Hibernate dynamically subclasses the proxied entity to create the proxy.
In the previous example Hibernate would create a subclass of
Address
and return that as a proxy to the real entity. When you call any of the getters or setters Hibernate will initialize the the entity from the database.
Unfortunately this technique can produce surprising results. Consider the following example classes:
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {
String name
}
class Owner {
Animal pet
}
Given you have an
Owner
with a
pet
association that is a
Dog
consider the following code:
def owner = Owner.get(1)
def pet = Animal.get(owner.petId)
if(pet instanceof Dog) {
// doesn't work!
}
Now you may think this code will work, but in fact it will not. The reason is Hibernate creates a dynamic proxy by subclassing
Animal
for the
owner.pet
association and caches it in the first level cache. So even if the actual proxied class is a
Dog
it won't be an instance of the
Dog
class due to the way proxies work.
The get around this problem GORM provides an
instanceOf
method that should always be used:
def owner = Owner.get(1)
def pet = Animal.get(owner.petId)
if(pet?.instanceof( Dog )) {
// this works
}
However, there are cases where this particular Hibernate abstraction may still leak through. For example:
def owner = Owner.get(1)
Dog pet = Animal.get(owner.petId)
In this case you will get a
ClassCastException
because the proxied
Animal
is not a
Dog
even though the actual instance
is a
Dog
.
Our best advice is to be aware of Hibernate proxies and how to deal with them when you do run into issues.