JPA实体的id字段在equals和hashCode中应该被考虑吗?

15

在使用JPA2和EclipseLink编写数据库应用程序的测试时,我遇到了一个问题:

我向数据库中添加了一些实体,稍后检索它并想将其与具有我期望的值的实例进行比较,以确认添加按照我的意图进行了。

起初,我编写了以下代码:

assertEquals(expResult, dbResult);

我的测试失败了,因为我无法确定由数据库生成的id字段的值,所以dbResult与我手动创建并填充的expResult不同。

我看到两种选择:

  • 要么我从equalshashCode方法中删除id字段,仅基于“真实值”进行比较。但我不知道这会不会在数据库或其他地方引起问题。

  • 要么我编写测试来手动检查除id之外的每个字段。

我该怎么做?

4个回答

15

关于这个问题,你可能会发现有很多争议。我的立场是,在你的应用程序中绝对不要使用数据库主键来做任何事情。它应该完全不可见。通过一些其他属性或属性组合来识别你的应用程序中的对象。

在“测试持久性操作”方面,你真正想要的可能是检查字段是否被正确保存和加载,以及在保存时是否分配了一些值给主键。这可能根本不是equals方法的工作。


8
可能会有很多争议?这还是比较乐观的说法。 - digitaljoel

8
在实现equalshashCode方法时,依赖于数据库生成的Id并不可取。你应该依赖于类的真正唯一/半唯一属性来检查相等性,并生成哈希码值。Hibernate documentation有一个广泛的页面讨论了这个问题,其中的事实几乎适用于每个JPA提供者。
使用业务键而不是数据库生成的值在equalshashCode方法中的根本原因是,JPA提供者在将实体持久化到数据库后必须实际发出SELECT语句。如果你使用数据库生成的Ids比较对象,那么在以下情况下会导致相等性测试失败:
  • 如果E1E2是类E的实体(使用数据库生成的Id验证相等性),那么如果E1E2尚未存储在数据库中,则它们将相等。这不是您想要的,特别是如果要在持久化之前将E1E2存储在某个Set中。如果E1E2的属性具有不同的值,情况会更糟;equals实现将阻止将两个显着不同的实体添加到Set中,而hashCode实现将在使用主键从HashMap查找实体时给出O(n)的查找时间。
  • 如果E1是已持久化的托管实体,E2是尚未持久化的实体,并且E1E2(除了Ids)的所有属性值都相似,则相等测试会认为E1!= E2。再一次,这可能不是您想要的,特别是如果您希望避免仅在其数据库生成的Ids不同的情况下重复实体在数据库中出现。
因此,equalshashCode 的实现应该使用业务键,以便对持久化和未持久化的实体表现出一致的行为。

“Hibernate文档”链接显示404错误。 - szx

7

根据《Hibernate实战》一书的建议,定义一个业务键并对其进行相等性测试。业务键是“一个属性或某些属性的组合,在具有相同数据库标识的每个实例中都是唯一的。” 在其他地方,它说不要将id用作这些属性之一,也不要使用集合中的值。


1

我会编写测试来明确检查字段。为了方便起见,在执行assertEqual测试之前,我将把预期和实际结果的ID设置为相同的预定义值,然后使用普通的equals方法。

仅仅因为测试有些困难,就去除等式中的ID是不合理的。这样做会放弃严重的性能优势和代码完整性。


不考虑id会带来哪些性能和完整性问题?检查数据库id是否与在我的equalshashCode方法中使用Object#hashCode是一样的吗? - soc
假设您有项目实体。您必须从服务层发送唯一项目的列表。现在有可能一个有问题的等于方法会将两个独特的记录视为相同的记录。拥有ID可以减少这个问题。此外,该解决方案并不具备可扩展性,如果我们决定更改使对象唯一的条件,就必须更新我们的等于方法。 - uncaught_exceptions
拥有一个保证唯一的ID非常适合哈希。我们保证避免哈希冲突。否则,哈希的质量将成为一个函数,取决于我们是否考虑了所有应该是唯一的属性并忽略了所有不是唯一的属性。 - uncaught_exceptions
我们有一个不同于数据库ID的“业务”ID,它是唯一的。这解决了问题吗? - soc
是的,我认为可以,只要它保证是唯一的即可。(抱歉我的先前评论中有很多错别字。我的借口是睡眠不足 :) )。 - uncaught_exceptions

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接