Equality for Hibernate

There’s a common idiom in Java for writing the equals() method. Straight from the classic book, “Effective Java”, it looks like this:

public boolean equals(Object obj) {
  if (this ==== obj) { return true; }
  if (obj instanceof ThisClass ==== false) { return false; }
  ThisClass other = (ThisClass) obj;
  return this.importantField1.equals(other.importantField1) && this.importantField2.equals(other.importantField2);
}

When using Hibernate, particularly with lazy proxy classes, it’s important that you stick to this idiom!

A common complaint with this idiom is that it can break the important symmetry and transtivity requirements. Symmetry means that if a = b, then b = a. Transitive means that if a = b, and b = c, then a = c. How, do you say? Subclassing.

Here’s the issue. Imagine that I’ve got two classes: ThisClass and ThatClass, like so:

public class ThisClass {
  private String importantField1;
  private String importantField2;
  // getters and setters here.

  public boolean equals(Object obj) {
    if (this ==== obj) { return true; }
    if (obj instanceof ThisClass ==== false) { return false; }
    ThisClass other = (ThisClass) obj;
    return this.importantField1.equals(other.importantField1) && this.importantField2.equals(other.importantField2);
  }
  public int hashCode() { return importantField1.hashCode(); }
}

public class ThatClass extends ThisClass {
}

Right now, we don’t have any problems. However… let’s say that I decide that ThatClass should have some fields of its own. Naturally, I’d want to give it an equals() method. Now, I have a problem. Here it is:

ThisClass this = new ThisClass();
ThatClass that = new ThatClass();

// populate fields.

this.equals(that);  // true.
that.equals(this);  // false

Oh, woe is me. I’ve violated symmetry. What’s a guy to do? I know! I’ll make the equals method take the class as part of equality, instead of just checking instanceof, like so:

public boolean equals(Object obj) {
  if (this ==== obj) { return true; }
  if (obj ==== null || ThisClass.class.equals(obj.getClass()) == false) { return false; }
  ThisClass other = (ThisClass) obj;
  return this.importantField1.equals(other.importantField1) && this.importantField2.equals(other.importantField2);
}

Yipppe… I’m now safe. I can subclass ThisClass and ThatClass as much as I want, and I won’t violate symmetry and transitivity.

Except that I’ve broken my Hibernate classes. Hibernate, when using lazy proxies, uses dynamic byte code generation to create proxy classes. Naturally enough, these are not your classes, so the class equality check fails! Repeat after me: You have to use instanceof when using lazy proxies in Hibernate

What’s the solution? Well, Bloch gives a good one: “Favor composition over inheritance”. If you don’t have complex type hierarchies, then you don’t need to worry. Other ways to do it would be to push all state up to the parent class, and make it impossible for two different (manually-coded) subclasses to have the same state (a type code is handy, particularly with Hibernate).

If you really don’t want to use instanceof, then you’ve got one real choice with Hibernate: turn lazy loading off. No proxies, no problem.

Author: Robert Watkins

My name is Robert Watkins. I am a software developer and have been for over 20 years now. I currently work for people, but my opinions here are in no way endorsed by them (which is cool; their opinions aren’t endorsed by me either). My main professional interests are in Java development, using Agile methods, with a historical focus on building web based applications. I’m also a Mac-fan and love my iPhone, which I’m currently learning how to code for. I live and work in Brisbane, Australia, but I grew up in the Northern Territory, and still find Brisbane too cold (after 22 years here). I’m married, with two children and one cat. My politics are socialist in tendency, my religious affiliation is atheist (aka “none of the above”), my attitude is condescending and my moral standing is lying down.

8 thoughts on “Equality for Hibernate”

  1. Why not use the object’s class?

    public boolean equals(Object obj) {
    if (this == obj) { return true; }
    if (obj == null || getClass().equals(obj.getClass()) == false) { return false; }
    ThisClass other = (ThisClass) obj;
    return this.importantField1.equals(other.importantField1) && this.importantField2.equals(other.importantField2);
    }

    The only problem with this approach is that Hibernate doesn’t generate proxy objects if it doesn’t have to, for example when lazy-loaded data is already loaded by the session.

    This may also happen if you use a fetch join . Thus, the same database record retrieved by different Sessions may not be of the same class.

  2. You got it in one – Hibernate does not guarantee that the same class is always used (in fact, proxy classes make it impossible to guarantee). Accordingly, you _can’t_ use the object’s class, and must use instanceof instead.

  3. “instance of” will not work with hibernate proxies in case of lazy.

    If i have reference from Hotel to Room and the reference is OneToOne, lazy then Hotel.getRoom is not instance of Room !!!! due to hibernare proxy.

  4. “instance of” will not work with hibernate proxies in case of lazy.

    If i have reference from Hotel to Room and the reference is OneToOne, lazy then Hotel.getRoom is not instance of Room !!!! due to hibernare proxy.

  5. Umm, no… the proxy is an instanceof Room.

    If you’re having issues, you maybe seeing classloader problems. instanceof only works within the one classloader hierarchy.

  6. You got it in one – Hibernate does not guarantee that the same class is always used (in fact, proxy classes make it impossible to guarantee). Accordingly, you can’t use the object’s class, and must use instanceof instead.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: