这里有一些关于JPA实体的讨论,以及应该为JPA实体类使用哪些hashCode()/equals()实现。它们中的大多数(如果不是全部)依赖于Hibernate,但是我想中立地讨论它们的jpa实现(顺便说一下,我使用的是EclipseLink)。
所有可能的实现都有其自身的优点和缺点:
hashCode()/equals()契约一致性(不可变性)用于列表/集操作
是否可以检测到相同的对象(例如来自不同会话的对象,来自惰性加载数据结构的动态代理)
实体在分离(或非持久化)状态下是否正确运行
在我看来,有三种选择:
Do not override them; rely on Object.equals() and Object.hashCode()
hashCode()/equals() work
cannot identify identical objects, problems with dynamic proxies
no problems with detached entities
Override them, based on the primary key
hashCode()/equals() are broken
correct identity (for all managed entities)
problems with detached entities
Override them, based on the Business-Id (non-primary key fields; what about foreign keys?)
hashCode()/equals() are broken
correct identity (for all managed entities)
no problems with detached entities
我的问题是:
我是否错过了一个选择和/或赞成/反对的观点?
你选择了什么,为什么?
更新1:
通过“hashCode()/equals()是坏的”,我的意思是连续的hashCode()调用可能返回不同的值,这(当正确实现时)在对象API文档的意义上不是坏的,但是当试图从Map、Set或其他基于哈希的集合中检索更改的实体时,会导致问题。因此,JPA实现(至少是EclipseLink)在某些情况下不能正确工作。
更新2:
谢谢你的回答——大部分问题都很有质量。
不幸的是,我仍然不确定哪种方法最适合实际应用程序,或者如何确定最适合我的应用程序的方法。所以,我将保持这个问题的开放性,希望有更多的讨论和/或意见。
我同意Andrew的回答。我们在应用程序中做同样的事情,但不是将uuid存储为VARCHAR/CHAR,而是将其分割为两个长值。请参阅UUID.getLeastSignificantBits()和UUID.getMostSignificantBits()。
还有一件事需要考虑,对UUID. randomuuid()的调用非常慢,因此您可能希望只在需要时才惰性地生成UUID,例如在持久化期间或调用equals()/hashCode()期间
@MappedSuperclass
public abstract class AbstractJpaEntity extends AbstractMutable implements Identifiable, Modifiable {
private static final long serialVersionUID = 1L;
@Version
@Column(name = "version", nullable = false)
private int version = 0;
@Column(name = "uuid_least_sig_bits")
private long uuidLeastSigBits = 0;
@Column(name = "uuid_most_sig_bits")
private long uuidMostSigBits = 0;
private transient int hashCode = 0;
public AbstractJpaEntity() {
//
}
public abstract Integer getId();
public abstract void setId(final Integer id);
public boolean isPersisted() {
return getId() != null;
}
public int getVersion() {
return version;
}
//calling UUID.randomUUID() is pretty expensive,
//so this is to lazily initialize uuid bits.
private void initUUID() {
final UUID uuid = UUID.randomUUID();
uuidLeastSigBits = uuid.getLeastSignificantBits();
uuidMostSigBits = uuid.getMostSignificantBits();
}
public long getUuidLeastSigBits() {
//its safe to assume uuidMostSigBits of a valid UUID is never zero
if (uuidMostSigBits == 0) {
initUUID();
}
return uuidLeastSigBits;
}
public long getUuidMostSigBits() {
//its safe to assume uuidMostSigBits of a valid UUID is never zero
if (uuidMostSigBits == 0) {
initUUID();
}
return uuidMostSigBits;
}
public UUID getUuid() {
return new UUID(getUuidMostSigBits(), getUuidLeastSigBits());
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = (int) (getUuidMostSigBits() >> 32 ^ getUuidMostSigBits() ^ getUuidLeastSigBits() >> 32 ^ getUuidLeastSigBits());
}
return hashCode;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof AbstractJpaEntity)) {
return false;
}
//UUID guarantees a pretty good uniqueness factor across distributed systems, so we can safely
//dismiss getClass().equals(obj.getClass()) here since the chance of two different objects (even
//if they have different types) having the same UUID is astronomical
final AbstractJpaEntity entity = (AbstractJpaEntity) obj;
return getUuidMostSigBits() == entity.getUuidMostSigBits() && getUuidLeastSigBits() == entity.getUuidLeastSigBits();
}
@PrePersist
public void prePersist() {
// make sure the uuid is set before persisting
getUuidLeastSigBits();
}
}
请考虑以下基于预定义类型标识符和ID的方法。
JPA的具体假设:
具有相同“类型”和相同非空ID的实体被认为是相等的
非持久化实体(假设没有ID)永远不等于其他实体
抽象实体:
@MappedSuperclass
public abstract class AbstractPersistable<K extends Serializable> {
@Id @GeneratedValue
private K id;
@Transient
private final String kind;
public AbstractPersistable(final String kind) {
this.kind = requireNonNull(kind, "Entity kind cannot be null");
}
@Override
public final boolean equals(final Object obj) {
if (this == obj) return true;
if (!(obj instanceof AbstractPersistable)) return false;
final AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
return null != this.id
&& Objects.equals(this.id, that.id)
&& Objects.equals(this.kind, that.kind);
}
@Override
public final int hashCode() {
return Objects.hash(kind, id);
}
public K getId() {
return id;
}
protected void setId(final K id) {
this.id = id;
}
}
具体实体示例:
static class Foo extends AbstractPersistable<Long> {
public Foo() {
super("Foo");
}
}
测试的例子:
@Test
public void test_EqualsAndHashcode_GivenSubclass() {
// Check contract
EqualsVerifier.forClass(Foo.class)
.suppress(Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS)
.withOnlyTheseFields("id", "kind")
.withNonnullFields("id", "kind")
.verify();
// Ensure new objects are not equal
assertNotEquals(new Foo(), new Foo());
}
主要优势:
简单
确保子类提供类型标识
使用代理类预测行为
缺点:
要求每个实体调用super()
注:
使用继承时需要注意。例如,类A和类B扩展A的实例相等性可能取决于应用程序的具体细节。
理想情况下,使用业务密钥作为ID
期待您的评论。