ObjectHunter
Hunting and gathering in the JEE landscape

I am Frank, a freelance Java developer specialized in backend development from south western germany.

A way around LazyInitializationExceptions without keeping sessions open

posted by fas on 2010-07-26 . Tagged as java, hibernate

Let me explain the problem first:
When retreiving Objects from Hibernate it is vital that the Session stays open if collections are to be fetched later. This is because Hibernate uses proxies for collections which only fetch data from the DB if it is actually needed.

Take a look at this simple Entities:

@Entity
public class Person{
    @Id
    @GeneratedValue
    private long id;
    private String name;
    @OneToMany(targetEntity=Pet.class,mappedBy="owner")
    private List<Pet> pets;
    [...getters/setters...]
}

@Entity
public class Pet{
    @Id
    @GeneratedValue
    private long id;
    private String name;
    @ManyToOne(targetEntity=Person.class)
    private Person owner;
    [...getters/setters...]
}


When fetching a Person from the DB, the collections pets will not hold the explicit Pet Objects associated with the Person, but a list of proxies, which fetch the needed data from the DB just in time:

Person p=session.get(Person.class,1); // here only the person's details get fetched from the db
p.getPets(); // it's only here that the Pets get actually fetched from the DB layer.


So when the Session gets closed before the call to p.getPets() hibernate will raise a LazyInitializationException.

There are multiple solutions for this problem:
1.) If there's only one collection in the Entity one can Hibernate let use JOINs to fetch the collection as soon as the Entity is retrieved from the DB.
2.) It's possible to keep the Session open while there can be requests to the DB by using OpenSessionInViewFilter
3.) Add a extra method for every combination of collections one wants hibernate to initialize (e.g. getPersonWithPetsInitialized(long id))

But i was never really happy with those solutions, since they all have some drawbacks, like OpenSessionInViewFilter has negative effects on scaling since the Sessions are open longer.

But since it is possible to have Hibernate initialize the collections you will need by using Hibernate.initialize(Object proxy) I implemented following method in the base class of my DAOs. This lets me fetch any Entity from the presentation layer with collections initialized by Hibernate. By using the Reflection API it is possible to define the collection property names as Strings beforehand, which get translated into method calls:


    public <T extends Object> T getEntity(Class<T> clazz,long id,String[] collectionsToBeInitialized){
        T entity=(T) this.getCurrentSession().createCriteria(clazz).add(Restrictions.idEq(id)).setFetchMode(collectionsToBeInitialized[0], FetchMode.JOIN).uniqueResult();
        int length=collectionsToBeInitialized.length;
        for (int idx=1;idx<length;idx++){
            String collectionName=collectionsToBeInitialized[idx];
            try {
                Method m = clazz.getMethod("get" + collectionName.substring(0, 1).toUpperCase() + collectionName.substring(1),(Class<T>) null);
                Hibernate.initialize(m.invoke(entity,(Object[]) null));
            } catch (NoSuchMethodException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            } catch (InvocationTargetException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            } catch (IllegalAccessException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            }
        }
        return entity;
    }



In some DAO child class the method getEntity() gets used if the String contains more than one collection property name. if it's null we obviously havent got to do anything, if theres at least collection defined in the array, the first collections get fetched from the DB using a SQL JOIN and the rest of the collections will be fetched using SELECTs.

    public Abstract getAbstractById(long id, String[] collectionsToBeInitialized) {
        Abstract a = null;
        if (collectionsToBeInitialized == null) {
            a = (Abstract) this.getCurrentSession().get(Abstract.class, id);
        } else {
            a = getEntity(Abstract.class, id, collectionsToBeInitialized);
        }
        return a;
    }

Tags: java, hibernate