JPA 实体
equals()
和 hashCode()
方法的历史很久以前就开始了。有大量的讨论,只需谷歌搜索“jpa equals hashcode”,你就会发现一篇精彩的Vlad Mihalcea文章,试图找到JPA Buddy团队的差距,这里有很多帖子在 S/O 等等。
当然,本文讨论的是没有业务密钥可以依赖的情况,唯一的选择就是使用自动生成的序列密钥。
首先,我想知道
@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();
}
}
缺点:
id
是 Long
,如果您将 Long
和 Integer
混合用于 ids,则可以轻松参数化优点:
equals()
和hashCode()
,只需从中扩展你的实体即可toString
实施不是强制性的,而是一种奖励。实体当然可以覆盖它(不过要避免隐式额外加载!)任何反馈都将受到高度赞赏。
附注
如果缺点对你来说是一个障碍,我想没有什么比使用 Vlad 的解决方案更好的了,并将这个片段放在 every 实体
@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();
}
另一种选择是考虑这种方法。
我相信实体(我的意思是带有
@Entity
和 @Id
注释的类的对象)应该 永远不会逃脱事务边界。
通常它们已经或可能通过
LAZY
关联的代理得到增强。这种“魔法”只有在交易中才能正确发挥作用。
假设您知道默认的 spring.jpa.open-in-view=true
是 反模式,应更改为 false
。
所以,如果你
em.detach()
em.merge()
(你是否清楚地了解为什么要使用它?)然后无需重写
equals()
和hashCode()
方法的默认实现。一切都会正常工作。
附注事务边界之外的实体还如何使用?他们必须首先转换为 DTO。我建议使用 MapStruct,因为它是唯一具有 compile 转换检查功能的一个。