Tuesday, December 30, 2008

Hibernate and EnhancerByCGLIB issues

Working with Hibernate can make performing routine database operations a breeze. Every now and then however you hit a snag that causes your brain to drop on the floor. I recently ran across one of these situations where objects were not being updated. After checking my syntax and inspecting objects, I realized the issue had to do with the fact that Hibernate will often create proxy objects as classes of type EnhancerByCGLIB when pulling collections that are marked as FetchType.LAZY. It also occurs when the SessionFactory is asked to load() the object instead of get().


First Case : Lazy Initialization Proxies:

The proxies may often contain all the information in your persistent class, and so they appear to be the real deal. When we query these objects they return all the information in the underlying proxied object. We run into a couple issues however if we use standard implementations of equals in our persistent class:

Lets say your equals() method in the class Noodle looks like this:

public boolean equals(Object obj) {
if(this == obj)
return true;
if(getClass() != obj.getClass() )
return false;
........
}


At this point we see that equals will never succeed - the class Noodle will never equal the Hibernate Proxy class. When you try to update an object from the collection, Hibernate never finds the object it needs to update because the equals implementation always fails. If you need to implement a comparison of class you could use one of the following :

this.getClass().isAssignableFrom(obj.getClass())

or

HibernateProxyHelper.getClassWithoutInitializingProxy(obj)

If you use hibernate annotations in your pojos, this additional Hibernate checking should not be too invasive or out of place. One other mistake people often make when implementing equals is to try to access the fields directly off of the object:


public boolean equals(Object obj) {
....
Noodle other = (Noodle)obj;
if(! this.attributeA.equals(other.attributeA) )
return false;
....
}


Here we run into 2 issues:

1. The cast may throw a ClassCastException.
2. The attributeA may have limited visibility.

A Solution that worked for me and covers the situation where the other object is not a proxy:

public boolean equals(Object other) {

if ( this == obj )
return true;

if( obj == null )
return false;

if( getClass().equals( obj.getClass() ) {
Noodle other = (Noodle)obj;
if( getId().equals( other.getId() )
return true;
}

else if( obj instanceof HibernateProxy ) {
if(HibernateProxyHelper.getClassWithoutInitializingProxy(obj).equals(this.getClass() ) {
Noodle other = (Noolde)obj;
if( getId().equals( other.getId() )
return true;
}
}

return false;
}

Second Case: You used Load() instead of get():

There may be times when you want to use load() - when you are simply adding references to an object in a collection for instance. But when you are pulling an object specifically to use its values, you should switch to using get() - as the attributes are guaranteed to be available.


3 comments:

Unknown said...

Hi,
I've got the same problem. You can simply replace the "getClass" by an "instanceof", it works great.
The problem I've got is that I'm writing my equals method for a child class.
Consider you have two classes, "Father" and "Child".
If he "Child" equals method, if you write
"obj instanceof Child" you will always get false, because obj is an instance of Father. It is the same with your method : HibernateProxyHelper.getClassWithoutInitializingProxy(obj) will return Father...
If you've got a solution to this problem let me know :)

Bob Hedlund said...

The simplest solution would be to use the knowledge that the call always returns father when it is the child object. If this is true, you can safely do the cast.

eugenioflima said...

It's still useful.

Thanks.