当没有自然键可用时,equals() 和 hashCode() 的实现?
Posted
技术标签:
【中文标题】当没有自然键可用时,equals() 和 hashCode() 的实现?【英文标题】:Implementation of equals() and hashCode() when no natural key is available? 【发布时间】:2011-10-01 16:00:28 【问题描述】:这个问题基本上是问题的后续:
Should I write equals() methods in JPA entities? 和 What is the best practice when implementing equals() for entities with generated ids
先来点背景...
你可以经常遇到以下主键星座:
-
自然键(业务键):通常是实体的一组真实的多列属性
人工键(代理键):无意义,通常是自动递增的(IDENTITY、AUTO_INCREMENT、AUTOINCREMENT、SEQUENCE、SERIAL 等)ID
混合键(半自然/半人工键):通常由人工 ID 和一些附加的自然列组成,例如,任何引用另一个使用 ID 并扩展该键的表的表(entity_id、ordinal_nbr ) 或类似的。
常见情况:对根、分支或叶继承表的多对一引用,它们都通过识别关系/依赖键共享一个通用的“愚蠢”ID。 当另一个表需要引用所有实体类型时,根(和分支)表通常是有意义的,例如PostAddresses -> Contacts,其中 Contacts 有子表 Persons、Clubs、 和设施,它们没有任何共同点,只是“可接触”。
现在到 JPA:
在 Java 中,我们可以创建新的实体对象,其 PK 可能不完整(null 或部分 null),一个 DBMS 最终会阻止我们插入 DB 的实体(行)。
但是,在使用应用程序代码时,拥有可以与现有(托管)实体进行比较的新(或分离)实体通常很方便,即使新实体对象还没有 PK 值。要为任何具有自然键列的实体实现这一点,请将它们用于实现 equals() 和 hashCode()(正如其他两个 SO 帖子所建议的那样)。
问题:
但是当无法确定自然/业务键时,您会怎么做,例如 Contacts 表,它基本上只是一个 ID(加上一个鉴别器)?基于 equals() 和 hashCode() 实现的 列选择策略 是什么? (上面的人工钥匙2.和3.)
显然没有太多选择......
一个(天真的)目标是实现相同的“瞬时可比性”。可以做到吗?如果不是,那么人工 ID equals() 和 hashCode() 实现的一般方法是什么样的?
注意:我已经在使用 Apache EqualsBuilder 和 HashCodeBuilder...我故意“天真化”了我的问题。
【问题讨论】:
【参考方案1】:如果您无法在对象上找到一组能够将其与其他同类对象区分开来的属性,那么您就无法比较这些对象,对吗?如果您提供详细的用例,可能会有更多用例,但在与 id 和鉴别器联系的情况下,在没有 id 的情况下,您只能比较具有相同鉴别器的对象组。如果保证组只有一个元素,那么鉴别器就是你的关键。
【讨论】:
请注意,不能使用鉴别器列,因为它不能识别。它设置为引用 super 的子实体的类型。每个子类型人员、俱乐部和设施可以有许多实体共享相同的值。在我看来,答案似乎只能是使用联系人 ID。 也许子实体提供了一些东西,我还没有检查过。 另请注意 Contacts.id 是可用。真的没有更多的东西,而是把这个 ID 扔给 Apache 构建器?【参考方案2】:通常建议的技术之一是使用 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 字段。像魅力一样工作。
【讨论】:
我认为问题是'it's often handy to have new (or detached) entities that can be compared to existing (managed) entities'
。那么如何将这些新的 UUID 与现有对象进行比较呢?
我明白你的意思,但实际上我没有遇到任何用例。我的回答基于最初的问题:“但是当无法确定自然/业务键时,你会怎么做,例如 Contacts 表,它基本上只是一个 ID(加上一个鉴别器)?会是什么?一个好的列选择策略,用于基于 equals() 和 hashCode() 实现?(上面的人工键 2. 和 3.)显然没有太多选择......”。我的解决方案解决了这个问题。
我也没有看到好的用例,所以我想澄清一下。
我不想让我的所有实体都扩展另一个类,因为并非所有实体都使用哑 (UU)ID。大多数PK实际上是自然的。我只是忍不住介绍继承关系的ID。这会导致混合类型的密钥,如 3.“混合密钥”中所述。
我认为您可以将 uuid 更好地标记为瞬态。如果您不希望实体扩展基类,您可以使用 aspectj ITD 添加任何想要的行为。【参考方案3】:
我认为这个主题比讨论所指向的更简单。
如果存在则获取数据库 ID,否则使用 Object#equals / 对象标识
为什么?如果将新实体放入数据库,JPA 只会将新生成的 id 从数据库映射到实体对象身份。另一方面,这意味着对象标识也是事先的主键。
讨论的重点似乎是假设,即两个具有相同属性的业务对象是相等的。但他们不是。 例如。仅当您不想重复地址值时,具有相同街道和城市的两个地址才相等。但是随后您也将它们设置为数据库中的主键,这导致您始终为您的业务对象获取主键。 如果您允许业务对象的地址重复,则对象身份是主键,因为它是两个地址之间的唯一区别。
在持久化一个实体后,数据库 id 确实完成了这项工作,因为您现在可以拥有仅共享相同数据库 id 的同一实体的克隆。 (但现在可以有多个内存位置/对象标识)
【讨论】:
以上是关于当没有自然键可用时,equals() 和 hashCode() 的实现?的主要内容,如果未能解决你的问题,请参考以下文章