JPA实体equals()/hashCode()的又一种实现方案及探讨
JPA自增主键实体的equals()与hashCode()实现方案
JPA实体的equals()与hashCode()方法一直是开发者热议的话题,搜索相关关键词能找到不少优质内容,包括Vlad Mihalcea的深度分析文章、JPA Buddy团队对现有方案不足的探讨,以及Stack Overflow上的大量讨论帖。本文聚焦无业务键可用、只能依赖自动生成序列主键的场景。
我一直有两个疑问:一是Hibernate为何没有给出官方的实现指导;二是Lombok至今没推出类似@EqualsAndHashCodeOfSyntheticId的注解——毕竟现在多数文章都指出,给实体使用常规的@EqualsAndHashCode是糟糕的做法,实际情况也确实如此。
结合Vlad与JPA Buddy的方案,我整理出另一种实现方案供大家参考:
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(); } }
缺点:
- 依赖Hibernate(不过提到JPA时通常默认使用Hibernate,影响不大)
- 采用继承方式实现
- 默认假设ID为Long类型,若混用Long和Integer可以轻松通过参数化适配
优点:
- 无需在每个实体中重复编写equals()和hashCode(),只需继承该抽象类即可
- 通过了Vlad的IdEqualityTest测试,覆盖了不同实体状态、代理对象、引用对比、集合存储等多种场景
- 提供了基础的toString实现(非必需,实体可自行重写,注意避免触发隐式的额外数据加载)
欢迎大家给出宝贵反馈。
补充:如果上述方案的缺点对你造成了阻碍,推荐使用Vlad的方案,为每个实体添加以下代码片段:
@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(); }
内容的提问来源于stack exchange,提问作者Andriy Slobodyanyk
相关产品推荐
相关产品推荐

