Thursday, January 7, 2010

Hibernate: disabling all lazy associations programmatically

In an ideal world, you would define a global fetch plan for your application, carefully weighing the pros and cons of making each association lazy.

In real life, you might find yourself forced to reuse existing mappings that use (and abuse of) lazy associations. This is annoying in a performance-critical use case, since you probably don't want 10 requests to go to the database for each loaded entity.

I found an old post about this issue on the Hibernate forums. "christian" of the Hibernate team (whom I assume is Christian Bauer) had this suggestion:

Usually, you override fetching strategies with HQL and Criteria queries. If you like to disable/enable some setting globally, you might rebuild the SessionFactory after modifying the metadata from the Configuration programatically. This is easier than it sounds, get all PersistentClass objects from Configuration (the API), loop over all collections and setLazy(true). Then, build a new SessionFactory from this Configuration.

This is acceptable for once-a-day batch operations, I think.
This is exactly what I needed. Here is an implementation:

public static Configuration setAllToLazy(final Configuration cfg) {
@SuppressWarnings("unchecked")
Iterator<PersistentClass> classes = cfg.getClassMappings();
while (classes.hasNext()) {
PersistentClass clazz = classes.next();
setPropertiesToLazy(clazz.getPropertyIterator());

Property key = clazz.getIdentifierProperty();
// Don't forget composite keys, which can contain associations
if (key.isComposite()) {
Component c = (Component) key.getValue();
setPropertiesToLazy(c.getPropertyIterator());
}
}

@SuppressWarnings("unchecked")
Iterator<Collection> collections = cfg.getCollectionMappings();
while (collections.hasNext()) {
Collection collection = collections.next();
collection.setLazy(true);
}
return cfg;
}

// Handle "simple" (*-to-one) properties
@SuppressWarnings("unchecked")
private static void setPropertiesToLazy(final Iterator iterator) {
Iterator<Property> properties = iterator;
while (properties.hasNext()) {
Value value = properties.next().getValue();
if (value instanceof ToOne) {
((ToOne) value).setLazy(true);
}
}
}

2 comments:

Sandeep Vaid said...

If we change the lazy properties using the above code and then try to
execute cfg.configure()

It gives error for the class whose configuration we have changed saying that this is the duplicate entity
org.hibernate.DuplicateMappingException: Duplicate class/entity mapping domain.CategoryDetail
at org.hibernate.cfg.Mappings.addClass(Mappings.java:118)
at org.hibernate.cfg.HbmBinder.bindRoot(HbmBinder.java:145)
at org.hibernate.cfg.Configuration.add(Configuration.java:669)
at org.hibernate.cfg.Configuration.addInputStream(Configuration.java:504)
at org.hibernate.cfg.Configuration.addResource(Configuration.java:566)

Sandeep Vaid said...

Sorry i got my problem.. Its working fine..

I made i mistake... I was calling cfg.configure();
Instead when i called cfg.buildSessionFactory(), it worked fine....

However if i have one-to-one association with constrained="false" then it cant be made lazy anyhow...