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@EqualsAndHashCodeto 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
idisLong, if you're mixing bothLongandIntegerfor ids it could be easily parameterized
Pros:
- there is no need to put the very same
equals()andhashCode()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
toStringimplementation 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();
}