Yet Another Implementation of JPA Entity equals()/hashCode()

187 Views Asked by At

The history of the JPA entity equals() and hashCode() methods started a long ago. There is a ton of discussions, just google for the "jpa equals hashcode" and you'll find a brilliant Vlad Mihalcea article, an attempt to find gaps there by the JPA Buddy team, a lot of posts here at S/O and so on.

Of course, the topic is about a case when there is no business key to rely on and the only choice is to use an auto-generated sequence key.

First, I wonder

  • why there is no official documentation from Hibernate
  • and why Lombok doesn't step in with some fancy @EqualsAndHashCodeOfSyntheticId (instead there is another ton of articles explaining that applying the regular @EqualsAndHashCode to the entities is a really bad idea. Indeed, it is)

Then combining both Vlad's and JPA Buddy's solutions I am presenting yet another one for your evaluation

public abstract class AbstractSyntheticIdEntity {
    public abstract Long getId();

    @Override
    public final boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;

        if (effectiveClass(this) != effectiveClass(o)) return false;
        AbstractSyntheticIdEntity that = (AbstractSyntheticIdEntity) o;
        return getId() != null && getId().equals(that.getId());
    }

    @Override
    public final int hashCode() {
        return effectiveClass(this).hashCode();
    }

    @Override
    public String toString() {
        return String.format(
                "%s(id=%d)",
                effectiveClass(this).getSimpleName(),
                getId()
        );
    }

    private static Class<?> effectiveClass(Object obj) {
        return obj instanceof HibernateProxy
                ? ((HibernateProxy) obj).getHibernateLazyInitializer().getPersistentClass()
                : obj.getClass();
    }
}

Cons:

  • it is Hibernate specific (I guess it is not a big issue, saying JPA we usually mean Hibernate)
  • uses inheritance
  • assumes that the id is Long, if you're mixing both Long and Integer for ids it could be easily parameterized

Pros:

  • there is no need to put the very same equals() and hashCode() everywhere, just extend your entity from it
  • it passes Vlad's IdEqualityTest (before jumping into the discussion, I kindly ask you to evalute your hypotesis against it) meaning that different entity states, proxies, references, sets and so on are taken into consideration
  • a base toString implementation is not mandatory and is a bonus. The entity could overwrite it of course (avoid implicit extra loading though!)

Any feedback is highly appreciated.

P.S.

If the cons are an impediment for you, I guess there is nothing better rather then using Vlad's solution and put this snippet for every entity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof CLASS_NAME)) return false;
        CLASS_NAME that = (CLASS_NAME) o;
        return id != null && id.equals(that.getId());
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
0

There are 0 best solutions below