19.1.3. Single-ended association proxies
Lazy fetching for collections is implemented using Hibernate's own implementation of persistent collections. However, a different mechanism is needed for lazy behavior in single-ended associations. The target entity of the association must be proxied. Hibernate implements lazy initializing proxies for persistent objects using runtime bytecode enhancement (via the excellent CGLIB library).
By default, Hibernate3 generates proxies (at startup) for all persistent classes and uses them to enable lazy fetching of many-to-one and one-to-one associations.
The mapping file may declare an interface to use as the proxy interface for that class, with the proxy attribute. By default, Hibernate uses a subclass of the class. Note that the proxied class must implement a default constructor with at least package visibility. We recommend this constructor for all persistent classes!
There are some gotchas to be aware of when extending this approach to polymorphic classes, eg.
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
Firstly, instances of Cat will never be castable to DomesticCat, even if the underlying instance is an instance of DomesticCat:
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
Secondly, it is possible to break proxy ==.
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
However, the situation is not quite as bad as it looks. Even though we now have two references to different proxy objects, the underlying instance will still be the same object:
cat.setWeight(11.0); // hit the db to initialize the proxy System.out.println( dc.getWeight() ); // 11.0
Third, you may not use a CGLIB proxy for a final class or a class with any final methods.
Finally, if your persistent object acquires any resources upon instantiation (eg. in initializers or default constructor), then those resources will also be acquired by the proxy. The proxy class is an actual subclass of the persistent class.
These problems are all due to fundamental limitations in Java's single inheritance model. If you wish to avoid these problems your persistent classes must each implement an interface that declares its business methods. You should specify these interfaces in the mapping file. eg.
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
where CatImpl implements the interface Cat and DomesticCatImpl implements the interface DomesticCat. Then proxies for instances of Cat and DomesticCat may be returned by load() or iterate(). (Note that list() does not usually return proxies.)
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();
Relationships are also lazily initialized. This means you must declare any properties to be of type Cat, not CatImpl.
Certain operations do not require proxy initialization
equals(), if the persistent class does not override equals()
hashCode(), if the persistent class does not override hashCode()
The identifier getter method
Hibernate will detect persistent classes that override equals() or hashCode().
By choosing lazy="no-proxy" instead of the default lazy="proxy", we can avoid the problems associated with typecasting. However, we will require buildtime bytecode instrumentation, and all operations will result in immediate proxy initialization.