这里有一些关于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:
谢谢你的回答——大部分问题都很有质量。
不幸的是,我仍然不确定哪种方法最适合实际应用程序,或者如何确定最适合我的应用程序的方法。所以,我将保持这个问题的开放性,希望有更多的讨论和/或意见。
我试着自己回答这个问题,直到我读了这篇文章,尤其是画了一个,我才完全满意找到的解决方案。我喜欢他懒创建UUID和最佳存储它的方式。
但我想增加更多的灵活性,即惰性创建UUID仅当hashCode()/equals()被访问时,第一次持久化实体与每个解决方案的优点:
Equals()表示“对象指向相同的逻辑实体”
尽可能使用数据库ID,因为为什么我要做两次工作(性能问题)
防止在尚未持久的实体上访问hashCode()/equals()时出现问题,并在它确实被持久后保持相同的行为
我真的很感激对我的混合解决方案的反馈如下
public class MyEntity {
@Id()
@Column(name = "ID", length = 20, nullable = false, unique = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
@Transient private UUID uuid = null;
@Column(name = "UUID_MOST", nullable = true, unique = false, updatable = false)
private Long uuidMostSignificantBits = null;
@Column(name = "UUID_LEAST", nullable = true, unique = false, updatable = false)
private Long uuidLeastSignificantBits = null;
@Override
public final int hashCode() {
return this.getUuid().hashCode();
}
@Override
public final boolean equals(Object toBeCompared) {
if(this == toBeCompared) {
return true;
}
if(toBeCompared == null) {
return false;
}
if(!this.getClass().isInstance(toBeCompared)) {
return false;
}
return this.getUuid().equals(((MyEntity)toBeCompared).getUuid());
}
public final UUID getUuid() {
// UUID already accessed on this physical object
if(this.uuid != null) {
return this.uuid;
}
// UUID one day generated on this entity before it was persisted
if(this.uuidMostSignificantBits != null) {
this.uuid = new UUID(this.uuidMostSignificantBits, this.uuidLeastSignificantBits);
// UUID never generated on this entity before it was persisted
} else if(this.getId() != null) {
this.uuid = new UUID(this.getId(), this.getId());
// UUID never accessed on this not yet persisted entity
} else {
this.setUuid(UUID.randomUUID());
}
return this.uuid;
}
private void setUuid(UUID uuid) {
if(uuid == null) {
return;
}
// For the one hypothetical case where generated UUID could colude with UUID build from IDs
if(uuid.getMostSignificantBits() == uuid.getLeastSignificantBits()) {
throw new Exception("UUID: " + this.getUuid() + " format is only for internal use");
}
this.uuidMostSignificantBits = uuid.getMostSignificantBits();
this.uuidLeastSignificantBits = uuid.getLeastSignificantBits();
this.uuid = uuid;
}
我试着自己回答这个问题,直到我读了这篇文章,尤其是画了一个,我才完全满意找到的解决方案。我喜欢他懒创建UUID和最佳存储它的方式。
但我想增加更多的灵活性,即惰性创建UUID仅当hashCode()/equals()被访问时,第一次持久化实体与每个解决方案的优点:
Equals()表示“对象指向相同的逻辑实体”
尽可能使用数据库ID,因为为什么我要做两次工作(性能问题)
防止在尚未持久的实体上访问hashCode()/equals()时出现问题,并在它确实被持久后保持相同的行为
我真的很感激对我的混合解决方案的反馈如下
public class MyEntity {
@Id()
@Column(name = "ID", length = 20, nullable = false, unique = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
@Transient private UUID uuid = null;
@Column(name = "UUID_MOST", nullable = true, unique = false, updatable = false)
private Long uuidMostSignificantBits = null;
@Column(name = "UUID_LEAST", nullable = true, unique = false, updatable = false)
private Long uuidLeastSignificantBits = null;
@Override
public final int hashCode() {
return this.getUuid().hashCode();
}
@Override
public final boolean equals(Object toBeCompared) {
if(this == toBeCompared) {
return true;
}
if(toBeCompared == null) {
return false;
}
if(!this.getClass().isInstance(toBeCompared)) {
return false;
}
return this.getUuid().equals(((MyEntity)toBeCompared).getUuid());
}
public final UUID getUuid() {
// UUID already accessed on this physical object
if(this.uuid != null) {
return this.uuid;
}
// UUID one day generated on this entity before it was persisted
if(this.uuidMostSignificantBits != null) {
this.uuid = new UUID(this.uuidMostSignificantBits, this.uuidLeastSignificantBits);
// UUID never generated on this entity before it was persisted
} else if(this.getId() != null) {
this.uuid = new UUID(this.getId(), this.getId());
// UUID never accessed on this not yet persisted entity
} else {
this.setUuid(UUID.randomUUID());
}
return this.uuid;
}
private void setUuid(UUID uuid) {
if(uuid == null) {
return;
}
// For the one hypothetical case where generated UUID could colude with UUID build from IDs
if(uuid.getMostSignificantBits() == uuid.getLeastSignificantBits()) {
throw new Exception("UUID: " + this.getUuid() + " format is only for internal use");
}
this.uuidMostSignificantBits = uuid.getMostSignificantBits();
this.uuidLeastSignificantBits = uuid.getLeastSignificantBits();
this.uuid = uuid;
}
实际上,似乎Option 2(主键)是最常用的。
自然的和不可变的业务密钥是很少的事情,创建和支持合成密钥对于解决情况来说太沉重了,这可能从来没有发生过。
看一下spring-data-jpa AbstractPersistable实现(唯一需要注意的是:对于Hibernate实现使用Hibernate. getclass)。
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (!getClass().equals(ClassUtils.getUserClass(obj))) {
return false;
}
AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
return null == this.getId() ? false : this.getId().equals(that.getId());
}
@Override
public int hashCode() {
int hashCode = 17;
hashCode += null == getId() ? 0 : getId().hashCode() * 31;
return hashCode;
}
注意在HashSet/HashMap中操作新对象。
相反,选项1(保留对象实现)在合并后被破坏,这是非常常见的情况。
如果你没有业务键,并且需要在哈希结构中操作新实体,则将hashCode重写为常量,如下所示Vlad Mihalcea的建议。