Pages

Sunday, March 4, 2012

Working with lazy associations


By default, Hibernate3 uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications.
If you set hibernate.default_batch_fetch_size, Hibernate will use the batch fetch optimization for lazy fetching. This optimization can also be enabled at a more granular level.
Please be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. For example:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
            
User u = (User) s.createQuery("from User u where u.name=:userName")
    .setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();

tx.commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // Error!
Since the permissions collection was not initialized when the Session was closed, the collection will not be able to load its state. Hibernate does not support lazy initialization for detached objects. This can be fixed by moving the code that reads from the collection to just before the transaction is committed.
Alternatively, you can use a non-lazy collection or association, by specifying lazy="false" for the association mapping. However, it is intended that lazy initialization be used for almost all collections and associations. If you define too many non-lazy associations in your object model, Hibernate will fetch the entire database into memory in every transaction.
On the other hand, you can use join fetching, which is non-lazy by nature, instead of select fetching in a particular transaction. We will now explain how to customize the fetching strategy. In Hibernate3, the mechanisms for choosing a fetch strategy are identical for single-valued associations and collections.
Select fetching (the default) is extremely vulnerable to N+1 selects problems, so we might want to enable join fetching in the mapping document:
<set name="permissions" 
            fetch="join">
    <key column="userId"/>
    <one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
The fetch strategy defined in the mapping document affects:
Irrespective of the fetching strategy you use, the defined non-lazy graph is guaranteed to be loaded into memory. This might, however, result in several immediate selects being used to execute a particular HQL query.
Usually, the mapping document is not used to customize fetching. Instead, we keep the default behavior, and override it for a particular transaction, using left join fetch in HQL. This tells Hibernate to fetch the association eagerly in the first select, using an outer join. In the Criteria query API, you would usesetFetchMode(FetchMode.JOIN).
If you want to change the fetching strategy used by get() or load(), you can use a Criteria query. For example:
User user = (User) session.createCriteria(User.class)
                .setFetchMode("permissions", FetchMode.JOIN)
                .add( Restrictions.idEq(userId) )
                .uniqueResult();
This is Hibernate's equivalent of what some ORM solutions call a "fetch plan".
A completely different approach to problems with N+1 selects is to use the second-level cache.

No comments:

Post a Comment