这是一个棘手的问题,hibernate 本身没有明确的答案。
John Bollinger 的回答涵盖了您的具体问题,但还有一些关于如何考虑平等和休眠的额外背景应该有助于弄清楚该怎么做。毕竟,鉴于 hibernate 不要求您做任何特别的事情,您可以做任何您想做的事,这导致了一个明显的问题:...好吧,那么我应该做什么呢?
这个问题归结为(使用Person作为模型类+关联表的任意示例;此外,假设person表具有生成的单个唯一ID(随机UUID或自动排序的整数值) .
Person 的实例代表什么?
大致有2个答案:
- 代表一个人。
person表中的一行还代表一个人;这两件事没有关系。
- 代表
person 表中的一行.
- 它代表我的应用程序中的一个状态,仅此而已。
尽管这些事情听起来很相似,但它们会导致关于平等的相反含义。
哪个选择是正确的?随你(由你决定。
继续阅读时,请记住:
任何尚未“保存”的 Person 实例都将具有 id 的 null 值,因为在插入时,hibernate 将要求 DB 为其生成一个值或自己生成一个值,然后才填充它在。
一个实例代表一行
- 第二种模式下的平等(
Person的一个实例代表表中的一行)应该看起来只要在 id 列,因为它定义了行的唯一性; person 表中一行的任何 2 个表示都保证引用同一行(因此,平等的) 当且仅当 id 相等时。这是一个必要条件和充分条件:如果它们相等,则 2 个对象必然引用同一行,如果它们不相等,则它们必然引用不同的行。
- 值得注意的是,如果
id仍然是null,那么他们不能相等,甚至对他们自己:更普遍的问题是:“这个对象代表行是否等于另一个对象代表行”如果这些对象代表行(未保存的行),则这是一个毫无意义的问题).如果您在每个对象上调用 save(),您最终会得到 2 行。最佳情况下,这样的对象应该被认为处于这样一种状态,即尝试对其调用 equals 是失败的,但 equals 的规范声明它们不能抛出,因此 false 是最佳答案。这意味着你想要:
class Person {
// fields
@Override public boolean equals(Object other) {
if (other == this) return true;
if (other == null || other.getClass() != Person.class) return false;
UUID otherId = ((Person) other).id;
return id == null ? false : id.equals(otherId);
}
}
这将您的 equals 方法定义为“最终代表同一行”。这成立即使你改变有意义的状态:
- 更改名称并保存对象?它......仍然是同一行,这个平等实施反映了这一点。
- 在比较中未保存时对每个调用 save()?然后你得到 2 行 - 这个平等实现之前反映了这一点和尝试保存后。
- 如果调用自身 (
a.equals(a)),则返回 true 作为平等规范的要求;它也适用于“建模一行”视图:如果您对同一个对象调用 save() 两次,它仍然只是一行。
一个实例代表一个人
一个人的本质与其获得的自动序列/自动生成 ID 完全无关;我们使用 hibernate 的事实是一个实现细节,在考虑平等时根本不应该发挥作用;毕竟,这个对象代表了一个人的概念,而这个概念完全独立于数据库而存在。数据库是为人建模的一件事;这个类的实例是另一个。
在这个模型中,您应该做完全相反的事情:找到可以唯一标识一个人本身的东西,并与之进行比较。毕竟,如果您在数据库中有 2 行都包含相同的社会安全号码,那么您只有 1 个人……而您恰好有 2 行都指的是同一个人。鉴于我们选择我们的实例来暗示它代表一个人,然后从 A 行加载的实例和从 B 行加载的实例应该被认为是相等的——毕竟,它们代表同一个人。
在这种情况下,您编写了一个考虑所有相关字段的 equals 方法除了autoseq/autogen ID 字段!如果有一个单独的唯一 ID,例如社会安全号码,请使用它。如果没有,基本上可以归结为比较所有字段的 equals 方法,除了ID。因为那是一个与定义一个人的东西绝对没有关系的领域。
实例定义应用程序中的状态
这几乎是一种逃避,通常意味着平等是无关紧要的/不适用的。这就像询问如何为 InputStream 实现实现一个 equals 方法——大多数情况下,你……不会。
在这里,默认行为(Object 自己的实现)是您想要的,因此,您既不实现hashCode也不实现equals。 Person 的任何实例都等于它自己(如 a.equals(a),相同的引用),而不等于任何其他实例,即使另一个实例的每个字段都具有相同的值,甚至 id 字段也不是 null (代表同一行)。
这样的对象不能有意义地用作值对象。例如,将此类内容填充到哈希图中是毫无意义的(充其量,您可以将它们填充到 IdentityHashMap 中,因为这些语义适用。进行任何查找的唯一方法是使用 .put() 的引用之前进入它并用它调用.get())。
哪一个是对的?由你决定。但要清楚地记录下来,因为根据我的经验,许多休眠用户绝对相信第一个或第二个模型是唯一的正确答案,并认为另一个答案完全是疯子。这是有问题的——他们会假设编写代码全部hibernate 模型类完全按照他们想要的方式工作,因此甚至不会考虑检查 docs/impl 来了解它的实际工作方式。
就其价值而言,对象就是对象,数据库行并不能很好地映射到对象的概念。 SQL 和 java 的 null 概念完全不兼容,并且“查询”的概念不能很好地映射到表(在选择表达式、选择视图和 JOIN 之间,这应该是显而易见的)——hibernate 正在向风车倾斜。这是一个有漏洞的抽象,这是它的许多漏洞之一。有漏洞的抽象可能很有用,只是要注意,在“边缘”,hibernate 试图兜售你的原则(对象可以表示查询结果和行)有你会遇到的限制。很多。