Hibernate, Javassist proxies and equals()

Hibernate uses Javassist proxies to help with lazy loading. If you’ve got no idea what that means, don’t worry – to use Hibernate, you’re not supposed to need to know anything about it. However, it is another one of those clever “transparent” framework programming techniques that can really give you some headaches in debugging if you don’t know it’s there (see http://lagod.id.au/blog/?p=266 for a couple more). There are four scenarios where the transparency gets a bit murky and you need to a know a bit more of what’s under the hood to avoid going crazy.

The first two, along with a good description of what Hibernate is doing with the proxies and why, can be found here: http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/. The third is any kind of reflective programming – see http://blog.jeremymartin.name/2008/02/java-reflection-hibernate-proxy-objects.html for a nice description of a typical issue and a good workaround. [Edit March 2014: Also applies to any framework that uses reflection, such as Apache BeanUtils/PropertyUtils].

The fourth, which is the one I really want to talk about, is the fact that the proxy actually is not strictly equal to the real object (in the sense of ==). Sounds obvious really. Not usually a problem, as you usually just have a reference to the proxy. However, there is a particular circumstance where you can end up with a reference to both the proxy and the real object. You will almost certainly believe them to be the identical object. There will be nothing about their behavior that will indicate to you that they are not, except for the fact that when you compare the with == you’ll get a false result. In fact, because the default implementation of equals() uses ==, the same applies to comparing them with equals().

You don’t need to do anything too wacky to end up in this very odd state. Consider this object:

class  MyClass {

Item head;

List items = new ArrayList(0);

// ... plus getters and setters
}

mapped to a database structure that looks like (pseudocode):

Table MyClass
Integer myClassID
Integer FK_headID

Table Item
Integer itemID
Integer FK_myClassID

So it’s a list of items, with an extra reference to one of the items broken out into a separate field for easy access. A bit denormalised, but if it’s a big list and it’s expensive to calculate “head”, it’s the kind of thing we might do without thinking too much about it. The important thing is that “head” is also in the list. Hibernate supports this mapping quite happily. You can load, manipulate and save this object graph all day long. However, this:

foreach (Item i: myClass.getItems()) {
  System.out.println(i.equals(myClass.getHead()));
}

will print false for every value in the list, even when you’ve confirmed that for one of them i.getItemId() == head.getItemId().

So what’s the trick? Using a standard many-to-one mapping for head, and a standard one-to-many mapping for the item list, myClass.getHead() returns a Javassist proxy object, and iterating through the list returns the unproxied object. If you look at it in a debugger, you can see that the real object backing the proxy is in fact the identical object that you get from the list – this is not a case where you’ve inadvertently mappedd one database record into two different in-memory objects. However, realobject != proxy (and vice versa).

The fix is easy. Although lazy loading is the default for Hibernate, it’s almost never a significant benefit for single objects. Simply set lazy=”false” in your mapping file for the many-to-one association. You can leave lazy loading enabled for the list, as it works by a different mechanism. If you really do need lazy loading for the single object and don’t mind a slightly more complex build configuration, there’s lazy=”no-proxy” (documented here: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching-proxies). And if you don’t mind Hibernate-specific code in your domain model (something I avoid like the plague, personally), you can also explicitly “unwrap” the proxy as described here: http://stackoverflow.com/questions/16383742/hibernate-javassistlazyinitializer-problems-with-validation.

So there you have it. A lesson in leaky abstraction, and, for a refreshing change, a head-scratching object equality problem that didn’t, for once, turn out to be some semi-intractable classloader wackiness.

Leave a Reply

Your email address will not be published. Required fields are marked *