是否应该在equals和hashCode中考虑JPA实体的id字段?
我在使用JPA2和EclipseLink编写数据库应用程序的测试时遇到了问题:
我将一些实体添加到数据库中,稍后检索它并希望将它与具有我期望确认添加按预期工作的值的实例进行比较。
首先我写了类似的东西
assertEquals(expResult, dbResult);
哪个失败了,因为我真的不知道id
字段的值,它是由数据库生成的,因此dbResult
与我用new
和手动填充创建的dbResult
不同。
我看到两个选择:
-
要么我从
equals
和hashCode
删除id
字段,以便比较仅基于“实际值”。 不过,我不知道这是否会导致数据库或其他地方出现问题。 -
或者我编写测试以手动显式检查除
id
之外的每个字段。
我该怎么办?
你可能会发现很多关于这个的争议。 我的立场是,绝对不要将数据库主键用于应用程序中的任何内容。 它应该是完全不可见的。 通过其他属性或属性组合识别应用程序中的对象。
在“测试持久性操作”前面,您真正想要的是检查字段是否已正确保存和加载,并且可能在保存时为主键分配了一些值。 这可能根本不是equals方法的工作。
从Hibernate in Action一书中,建议定义一个业务键并测试它的相等性。 业务键是“属性或属性的某种组合,对于具有相同数据库标识的每个实例都是唯一的。” 在其他领域,它表示不使用id作为其中一个属性,并且不使用集合中的值。
在您的equals
和hashCode
实现中依赖于数据库生成的ID是不可取的。 在检查相等性和生成哈希码值时,您应该依赖类的真正唯一/半唯一属性。 Hibernate文档有一个广泛的页面讨论这个,其中的事实适用于或多或少的每个JPA提供者。
在equals
和hashCode
实现中使用业务键而不是数据库生成的值的根本原因是JPA提供程序必须在将实体保留在数据库中之后实际发出SELECT
。 如果使用数据库生成的ID比较对象,那么在以下场景中最终会出现一个失败的相等测试:
- 如果
E1
和E2
是E类的实体(使用数据库生成的Idsvalidation相等),那么如果E1
和E2
尚未存储在数据库中则相等。 这不是你想要的,特别是如果想在持久性之前将E1
和E2
存储在某些Set
。 如果E1
和E2
的属性具有不同的值,则更糟;equals
实现将阻止将两个明显不同的实体添加到Set
,并且当使用主键从HashMap
查找实体时,hashCode
实现将为您提供O(n)
查找时间。 - 如果
E1
是一个已经被持久化的被管实体,并且E2
是一个尚未被持久化的实体,那么在E1
和E2
所有属性值的情况下,等式测试将认为E1
!=E2
(除了Ids) )是相似的。 同样,这可能不是您想要的,特别是如果您想避免数据库中仅在数据库生成的ID中不同的重复实体。
因此, equals
和hashCode
实现应该使用业务键,以便为持久化和非持久化实体展示一致的行为。
我会写我的测试来明确检查字段。 为了简化这一点,在执行assertEqual测试之前,我将预期和实际结果的id设置为相同的预定义值,然后使用正常的equals方法。
从equals中删除ID是不合理的,因为测试有点困难。 您将获得严重的性能优势和代码完整性。