Monday, November 22, 2010

Hibernate Lazy proxied objects and instanceof operation, type casting

Lazy loading of associations in Hibernate will help improve load time and keep performance reasonable. But there are some things to be aware of when lazily loading objects. The one I'm going to talk about today is the issue of using the instanceof operator and casting on a proxied object.

Say we have an inhertiance strategy where class Person is the super class, and class Student is a sublcass of Person.



If we configure hibernate to lazily load objects of class type Person, then Hibernate will create a proxied object that extends from class Person and not class Student even if the true class type of the object is a Student.

Then the following code will not work:

Person person = lazyLoadFromHibernate(id); // The proxied object from hibernateif (person instanceof Student) { // Fails on proxied object
    Student student = (Student) person; // Fails on proxied object
}

This becomes a real pain, when you already have this type of code everywhere in your application. The easy fix would be to set lazy="false" and enforce no proxied objects, but then your performance would take a hit.
So I implemented a solution that would always load the true object by replacing the proxied object with a fully initialized object and maintaining performance as follows:

    public static <T> T initializeAndUnproxy(T var) {
        if (var == null) {
            return null;
        }

        Hibernate.initialize(var);
        if (var instanceof HibernateProxy) {
            var = (T) ((HibernateProxy) var).getHibernateLazyInitializer().getImplementation();
        }
        return var;
    }

But I didn't want my application code at the business layer to be aware of Hibernate, so I implemented a Tuplizer and overrided the afterInitialize(). For example, if a Person object is associated with a Student object via a "classmate" relationship then I would override the Tuplizer method as follows:




    public void afterInitialize(Object entity, boolean lazyPropertiesAreUnfetched, SessionImplementor session) {
        super.afterInitialize(entity, lazyPropertiesAreUnfetched, session);
        // Check to see if the entity is a person 
        if (Person.class.isAssignableFrom(entity.getClass())) {    
            
            // Always provide the true implementation and replace the proxied object
            Person person = (Person) entity;
            Person classMate = (Person) entity.getClassMate();
            if ( classMate != null) {
                classMate = initializeAndUnproxy(classMate); // Fully Initialize the proxy
                person.setClassMate(class); // replace proxy
            }
        }
        
    }



References:

http://techscouting.wordpress.com/2010/09/24/instanceof-fails-with-hibernate-lazy-loading-and-entity-class-hierarchy/

http://stackoverflow.com/questions/2959475/hibernate-lazy-loading-proxy-incompatable-w-other-frameworks

2 comments:

  1. if (Person.class.isAssignableFrom(entity.getClass()))

    should be written as

    if (entity instanceof Person)

    ReplyDelete
  2. Is this not undoing the lazy initialization? I think the Tupilizer is called at the time the object is fetched (the parent object), which means the classmate will be eagerly fetched when the person is initialized... instead of when it is requested. Am I missing something?

    ReplyDelete