Google

Sep 18, 2012

Understanding Hibernate proxy objects and avoiding potential pitfalls


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 {

   private Long id;
   private String name;
   private String title;
   private Employee superior;
   private Set<Employee> subordinates;

   //getters and setters are omitted

}


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.

Q. What are some of the pitfalls of using a proxy object?
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

  @Override
  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
  }
 
As 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.

  @Override
  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
  }
 
Pitfall 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 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.

A proxy class is a subclass of a field type that is required. Hibernate creartes a dynamic subclass by looking at the type of field. This means that if the field type is not the actual implementation (e.g. CasualEmployee) type, but an interface or superclass (e.g. Employee), the type of the proxy will be different than the type of the actual object. So, as shown in the diagram below, If the field type is the superclass (e.g. Employee) of the actual implementation (i.e. CasualEmployee), the proxy type (i.e. proxy) and the implementation-type (e.g. CasualEmployee) will be siblings in the type hierarchy, both extending the superclass. This means the proxy object will not be an instance of the implementing type (i.e. CasualEmployee), and the application code depending on this check will fail.



To prevent this issue, you have two approaches.

Approach 1: Switch off 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.

Labels:

13 Comments:

Anonymous Anonymous said...

superior = HibernateUtils.unproxy(book);

What 'book' refers here?

4:20 PM, October 11, 2012  
Blogger Arulkumaran Kumaraswamipillai said...

It is the Hibernate entity. It should be employee, not book. Thanks for pointing that out.

4:30 PM, October 11, 2012  
Anonymous Naidu said...

can you provide Exception hirachy and Collection hirachy with examples

3:13 AM, December 14, 2012  
Anonymous Anonymous said...

This is highly impressive and extremely useful. Thanks a ton. Can I know, the currency of this article?

1:35 AM, January 06, 2013  
Anonymous Anonymous said...

I followed all of this article until the statement "When you have a type hierarchy as shown above, the type casts and instanceof operators will not work with the proxy objects." Can you explain why a dynamically created subclass would fail the instanceof check? Can you explain the reasons a dynamically created subclass of Employee passes instanceof Employee but a dynamic subclass of a subclass would not?

8:10 PM, July 17, 2013  
Blogger Arulkumaran Kumaraswamipillai said...

I have added updated it with more details.

9:34 AM, July 20, 2013  
Blogger Ashutosh Kumar said...

wowww!!!gr8..thanks,..

6:57 AM, July 23, 2013  
Blogger Vijay said...

Very very helpful...

6:59 PM, August 08, 2013  
Anonymous John said...

thank you sir for nice tutorial

4:44 AM, August 29, 2013  
Blogger Andres Gomez said...

Thank you, it was very helpful!

10:54 PM, September 03, 2013  
Anonymous Anonymous said...

Very Nice Article.

4:47 AM, February 06, 2014  
Blogger Chetan Patel said...

To solve pitfall 2, in equals method cant I use object instanceof subclass instead of parent class? Will it not solve problem?

2:57 PM, March 08, 2014  
Anonymous Anonymous said...

I have been using hibernate for long time. But I never knew the details listed here. Thanks for sharing.

1:23 PM, September 19, 2014  

Post a Comment

Subscribe to Post Comments [Atom]

Links to this post:

Create a Link

<< Home