Q. How does hibernate support lazy loading? A. Hibernate uses a proxy object to support lazy loading. Basically as soon as you reference a child or lookup object via the accessor/getter methods, if the linked entity is not in the session cache (i.e. the first-level cache), then the proxy code will go off to the database and load the linked object. It uses javassist (or CGLIB ) to effectively and dynamically generate sub-classed implementations of your objects. Let's look at an example. An employee hierarchy table can be represented in a database table as shown below public class Employee {In the above example, if you use lazy loading then the "superior" and "subordinates" will be proxied (i.e. not the actual object, but the stub object that knows how to load the actual object) when the main "Employee" object is loaded. So, if you need to get the "subordinates" or "superior" object, you invoke the getter method on the employee like employee.getSuperior( ) and the actual object will be loaded. |
A. The typical pitfall is in how you implement your equals( ) method that gets invoked when comparing objects.
Pitfall 1: As explained before, the proxy objects are dynamically created by sub-classing your object at runtime. The subclass will have all the methods of the parent, but the fields (e.g. name, etc) in the proxy object will remain null, and when any of the methods are accessed via getter/setter method, the proxy loads up the real object from the database.
A typical equals( ) method implementation will look like
@OverrideAs discussed before, the supplied object is a proxy object and the supplied name will be null. This can be fixed by using the getter method in Line Y instead of using the field directly. Using the getter method will tell the proxy object to load the actual object as shown below.
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Employee)) { //Line X: compare object type
return false;
}
return name.equals((Employee)obj).name); //Line Y: compare names
}
@OverridePitfall 2: We saw earlier that the the proxy objects are dynamically created by sub-classing your object. In a simple scenario where you only have the "Employee" object the typecasting in Line Y and "instanceof" operator in Line X will work. But, what will happen if you have a type hierarchy for The class Employee as shown below
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Employee)) {
return false;
}
return name.equals((Employee)obj).getName()); //Line Y: compare names
}
public class PermanentEmployee extends Employee {
.......
}
public class CasualEmployee extends Employee {
.......
}
When you have a type hierarchy as shown above, the type casts and instanceof operators will not work with the proxy objects. To prevent this issue, you have two approaches.
Approach 1: Switch of proxying on the top level class by setting lazy=”false”, which will turn proxying off for the hierachy.
Approach 2: Use the "Gang of Four" (i.e. GoF) visitor design pattern that allows you to adapt a single object or manipulate a collection of polymorphic objects without all the messy typecasts and instanceof operations.
Pitfall 3: As per the above example, if you have an “Employee” class, that contains a “name” property, when you invoke do “employee.getName()”, the proxies will get the "name" from Hibernate caches (either 1st or 2nd levels) or the database when requested. But if this call happens in the presentation layer like in the Struts action class, you will get the org.hibernate.LazyInitializationException because the Hibernate Session is closed and this lazy attribute does not have the session attached, hence can’t load their lazy references.
The solution is to de-proxy the employee class as shown below:
Step 1: Write a generic utility class to de-proxy a given object
public class HibernateUtil {
public static <T> T unproxy(T entity) {
if (entity == null) {
return null;
}
if (entity instanceof HibernateProxy) {
Hibernate.initialize(entity);
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}
return entity;
}
}
Step 2: Use the above utility class
public Employee getSuperior() {
superior = HibernateUtils.unproxy(employee);
return superior;
}
These types of issues are hard to debug, and being aware of these pitfalls can save you lots of time in debugging and fixing the issues.