111

How should model class's equals and hashcode be implemented in Hibernate? What are the common pitfalls? Is the default implementation good enough for most cases? Is there any sense to use business keys?

It seems to me that it's pretty hard to get it right to work in every situation, when lazy fetching, id generation, proxy, etc are taken into account.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
egaga
  • 21,042
  • 10
  • 46
  • 60

8 Answers8

83

Hibernate has a nice and long description of when / how to override equals() / hashCode() in documentation

The gist of it is you only need to worry about it if your entity will be part of a Set or if you're going to be detaching / attaching its instances. The latter is not that common. The former is usually best handled via:

  1. Basing equals() / hashCode() on a business key - e.g. a unique combination of attributes that is not going to change during object (or, at least, session) lifetime.
  2. If the above is impossible, base equals() / hashCode() on primary key IF it's set and object identity / System.identityHashCode() otherwise. The important part here is that you need to reload your Set after new entity has been added to it and persisted; otherwise you may end up with strange behavior (ultimately resulting in errors and / or data corruption) because your entity may be allocated to a bucket not matching its current hashCode().
fabien7474
  • 16,300
  • 22
  • 96
  • 124
ChssPly76
  • 99,456
  • 24
  • 206
  • 195
  • 1
    When you say "reload" @ChssPly76 you mean doing a `refresh()`? How does your entity, which obeys the `Set` contract end up in the wrong bucket (assuming you have a good enough hashcode implementation). – non sequitor Oct 28 '09 at 19:21
  • 4
    Refresh the collection or reload the entire (owner) entity, yes. As far as wrong bucket goes: a) you add new entity to set, its id is not set yet so you're using identityHashCode which places your entity in bucket #1. b) your entity (within set) is persisted, it now does have an id and thus you're using hashCode() based on that id. It's different from above and **would have** placed your entity in the bucket #2. Now, assuming you hold a reference to this entity elsewhere, try calling `Set.contains(entity)` and you'll get back `false`. Same goes for get() / put() / etc... – ChssPly76 Oct 28 '09 at 19:33
  • Makes sense but never used identityHashCode myself though I see it used in the Hibernate source like in their ResultTransformers – non sequitor Oct 29 '09 at 05:37
  • 1
    When using Hibernate, you could also run into [this problem](https://hibernate.atlassian.net/browse/HHH-3799), to which I still haven't found a solution. – Giovanni Botta Jul 18 '13 at 19:36
  • @ChssPly76 Due to business rules that determine if two object are equal I will need to base my equals/hashcode methods on properties that may change within an object's lifetime. Is that really a big deal? If so how do I get around it? – ubiquibacon Nov 20 '13 at 23:12
  • I haven't seen any evidence that equals/hashCode is required when attaching the entity back to a session. I think the doc is outdated. Unless someone has a proof of the opposite.. – Stanislav Bashkyrtsev Nov 11 '15 at 07:45
  • The [current documentation](http://docs.jboss.org/hibernate/stable/core/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode) reads quite different than the original version, it starts out saying you usually don't need to override equals() and hashCode() at all, since "Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a particular session scope.": – Stefan L Aug 10 '18 at 07:55
43

I don't think that the accepted answer is accurate.

To answer the original question:

Is the default implementation good enough for most cases?

The answer is yes, in most cases it is.

You only need to override equals() and hashcode() if the entity will be used in a Set (which is very common) AND the entity will be detached from, and subsequently re-attached to, hibernate sessions (which is an uncommon usage of hibernate).

The accepted answer indicates that the methods need to be overriden if either condition is true.

Phil
  • 548
  • 4
  • 4
  • This aligns with my observation, time to find out [why](http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/persistent-classes.html#persistent-classes-equalshashcode). – Vlastimil Ovčáčík Feb 11 '16 at 16:31
  • "You only need to override equals() and hashcode() if the entity will be used in a Set" is completely enough if some fields identify an object, and so you don't want to rely on Object.equals() to identify objects. – davidxxx Nov 11 '17 at 21:23
22

The best equals and hashCode implementation is when you use a unique business key or natural identifier, like this:

@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    @Column(unique = true, updatable = false)
    private String name;
 
    @Override
    public int hashCode() {
        HashCodeBuilder hcb = new HashCodeBuilder();
        hcb.append(name);
        return hcb.toHashCode();
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Company)) {
            return false;
        }
        Company that = (Company) obj;
        EqualsBuilder eb = new EqualsBuilder();
        eb.append(name, that.name);
        return eb.isEquals();
    }
}

The business key should be consistent across all entity state transitions (transient, attached, detached, removed), that's why you can't rely on id for equality.

Another option is to switch to using UUID identifiers, assigned by the application logic. This way, you can use the UUID for the equals/hashCode because the id is assigned before the entity gets flushed.

You can even use the entity identifier for equals and hashCode, but that requires you to always return the same [hashCode value so that you make sure that the entity hashCode value is consistent across all entity state transitions, like this:

@Entity(name = "Post")
@Table(name = "post")
public class Post implements Identifiable<Long> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    public Post() {}
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
 
        if (!(o instanceof Post))
            return false;
 
        Post other = (Post) o;
 
        return id != null &&
               id.equals(other.getId());
    }
 
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
  
    //Getters and setters omitted for brevity
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • +1 for the uuid approach. Put that into a `BaseEntity` and never think again about that problem. It takes a bit of space on the db side but that price you better pay for the comfort :) – Martin Frey Aug 25 '16 at 13:49
13

When an entity is loaded through lazy loading, it's not an instance of the base type, but is a dynamically generated subtype generated by javassist, thus a check on the same class type will fail, so don't use:

if (getClass() != that.getClass()) return false;

instead use:

if (!(otherObject instanceof Unit)) return false;

which is also a good practice, as explained on Implementing equals in Java Practices.

for the same reason, accessing directly fields, may not work and return null, instead of the underlying value, so don't use comparison on the properties, but use the getters, since they might trigger to load the underlying values.

stivlo
  • 83,644
  • 31
  • 142
  • 199
  • 1
    This works if you are comparing objects of concrete classes, which did not work in my situation. I was comparing objects of super classes, in which case this code worked for me: obj1.getClass().isInstance(obj2) – Tad Aug 07 '15 at 19:38
6

Yeah, it's hard. In my project equals and hashCode both rely on the id of the object. The problem of this solution is that neither of them works if the object has not been persisted yet, as the id is generated by database. In my case that's tolerable since in almost all cases objects are persisted right away. Other than that, it works great and is easy to implement.

Carlos
  • 2,503
  • 26
  • 29
  • What I think we did is to use object identity in the case where the id has not been generated – Kathy Van Stone Oct 28 '09 at 17:22
  • 2
    the problem here is that if you persist the object, your hashcode changes. That can have big detrimental results if the object is already part of a hash based data structure. So, if you do wind up using object identity, you'd better continue using obj id until the object is completely freed (or remove the object from any hash based structures, persist, then add it back in). Personally, I think it would be best to not use id, and base the hash on immutable properties of the object. – Kevin Day Oct 29 '09 at 03:55
1

In the documentation of Hibernate 5.2 it says you might not want to implement hashCode and equals at all - depending on your situation.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

Generally, two objects loaded from the same session will be equal if they are equal in the database (without implementing hashCode and equals).

It gets complicated if you're using two or more sessions. In this case, the equality of two objects depends on your equals-method implementation.

Further, you'll get into trouble if your equals-method is comparing IDs that are only generated while persisting an object for the first time. They might not be there yet when equals is called.

Nina
  • 681
  • 1
  • 10
  • 27
0

If you happened to override equals, make sure you fulfill its contracts:-

  • SYMMETRY
  • REFLECTIVE
  • TRANSITIVE
  • CONSISTENT
  • NON NULL

And override hashCode, as its contract rely on equals implementation.

Joshua Bloch(designer of Collection framework) strongly urged these rules to be followed.

  • item 9: Always override hashCode when you override equals

There are serious unintended effect when you don't follow these contracts. For example List#contains(Object o) might return wrong boolean value as the general contract not fulfilled.

Awan Biru
  • 373
  • 2
  • 10
-1

There is very nice article here: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Quoting an important line from the article:

We recommend implementing equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key, a key that would identify our instance in the real world (a natural candidate key):

In simple terms

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}
Ravi Shekhar
  • 2,633
  • 4
  • 19
  • 19