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
if (Person.class.isAssignableFrom(entity.getClass()))
ReplyDeleteshould be written as
if (entity instanceof Person)
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