Java 在没有可用的自然密钥时实现equals()和hashCode()?
这个问题基本上是以下问题的后续问题: 及 先了解一下背景 您经常会遇到以下主键星座:Java 在没有可用的自然密钥时实现equals()和hashCode()?,java,jpa,equals,identity,hashcode,Java,Jpa,Equals,Identity,Hashcode,这个问题基本上是以下问题的后续问题: 及 先了解一下背景 您经常会遇到以下主键星座: 自然键(业务键):通常是实体的一组真实的多列属性 人工关键点(代理关键点):无意义的,通常是自动增量(标识、自动增量、自动增量、序列、序列等)ID 混合键(半自然/半人工键):通常由一个人工ID和一些额外的自然列组成,例如引用另一个使用ID并扩展该键的表(实体ID、序号nbr)或类似的表 常见场景:对根、分支或叶继承表的多对一引用,这些表都通过标识关系/依赖键共享一个公共的“愚蠢”ID。 当另一个表需要引用所有
注意:我已经在使用Apache EqualBuilder和HashCodeBuilder。。。我故意把我的问题“naivified”了。如果你在对象上找不到一组能将其与其他同类对象区分开来的属性,那么你就不能比较这些对象,是吗?如果您提供了详细的用例,则可能会有更多内容,但如果您使用id和鉴别器,在没有id的情况下,您只能比较具有相同鉴别器的对象组。如果保证组只有一个元素,那么鉴别器就是关键。通常建议的技术之一是使用UUID作为标识符,这有两个缺点 它们会造成难看的URL,而且基于如此长的标识符查询实体可能会影响性能。长UUID还会导致数据库索引变得太大 UUID的优点是不必为每个实体实现单独的hashCode()equals()方法 我决定在自己的项目中使用的解决方案是混合使用传统的指定标识符,并在hashCode()equals()方法内部使用UUID。它看起来像这样:
@Configurable
@MappedSuperclass
@EntityListeners({ModelListener.class})
@SuppressWarnings("serial")
public abstract class ModelBase implements Serializable {
//~~ Instance Fields =====================================
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable=false, unique=true)
protected Long id;
@Column(name="__UUID__", unique=true, nullable=false, updatable=false, length = 36)
private String uuid = java.util.UUID.randomUUID().toString();
//~ Business Methods =====================================
@Override
public String toString() {
return new ToStringCreator(this)
.append("id", getId())
.append("uuid", uuid())
.append("version", getVersion())
.toString();
}
@Override
public int hashCode() {
return uuid().hashCode();
}
@Override
public boolean equals(Object o) {
return (o == this || (o instanceof ModelBase && uuid().equals(((ModelBase)o).uuid())));
}
/**
* Returns this objects UUID.
*
* @return - This object's UUID.
*/
public String uuid() {
return uuid;
}
//~ Accessor Methods ======================================
public Long getId() {
return id;
}
@SuppressWarnings("unused")
private void setId(Long id) {
this.id = id;
}
@SuppressWarnings("unused")
private String getUuid() {
return uuid;
}
@SuppressWarnings("unused")
private void setUuid(String uuid) {
this.uuid = uuid;
}
}
只需为所有实体扩展ModelBase。这种技术的优点是,一旦创建了对象,就可以分配uuid。但我们仍然可以在应用程序代码中使用指定的id来查询特定对象。基本上,uuid字段在我们的应用程序代码中从未使用过,甚至从未考虑过,只是出于比较的目的。工作起来很有魅力。我认为这个主题比讨论要简单得多 获取数据库id(如果存在),否则使用对象#equals/Object identity 为什么??若您将一个新实体放入数据库,JPA所做的只是将一个新生成的id从数据库映射到实体对象标识。这意味着另一方面,对象标识也是一个主键 讨论的重点似乎通常是假设具有相同属性的两个业务对象相等。但事实并非如此。 例如,只有在您不希望地址值重复的情况下,具有相同街道和城市的两个地址才是相等的。但是,您也将它们设置为数据库中的主键,这导致您始终为您的业务对象获取主键。 如果允许业务对象使用重复地址,则对象标识是主键,因为它是两个地址之间的唯一区别
在分配一个实体后,数据库id确实完全承担了任务,因为您现在可以拥有同一实体的克隆,而该实体只共享相同的数据库id(但现在可以有多个内存位置/对象标识)我认为问题在于,
“拥有可以与现有实体进行比较的新(或分离)实体通常很方便(托管)实体“
。那么您如何将这些新UUID与现有对象进行比较?我明白您的观点,但在实践中,我没有遇到任何用于此的用例。我的答案基于最初的问题:“但是,当无法确定自然/业务键时,如Contacts表,它基本上只是一个ID(加上一个鉴别器)时,您会怎么做?基于equals()和hashCode()实现的好列选择策略是什么?(上面的人工键2和3)显然没有太多选择。。。“。我的解决方案解决了这个问题。我也没有看到好的用例,这就是为什么我想澄清它。我不想让我所有的实体都扩展