39

这是来自Hibernate 官方教程

有一个替代<composite-id>声明允许使用复合键访问遗留数据。强烈建议不要将其用于其他任何用途。

为什么不鼓励复合键?我正在考虑使用一个 3 列表,其中所有列都是外键,并且一起形成一个主键,这在我的模型中是一个有意义的关系。我不明白为什么这是一个坏主意,尤其是我将在它们上使用索引。

有什么选择?创建一个额外的自动生成的列并将其用作主键?无论如何,我仍然需要查询我的 3 列!?

简而言之,为什么这个说法是正确的?什么是更好的选择?

4

6 回答 6

46

他们不鼓励他们有几个原因:

  • 它们使用起来很麻烦。每次您需要引用一个对象(或行)时,例如在您的 Web 应用程序中,您需要传递 3 个参数而不仅仅是一个。
  • 他们效率低下。数据库需要对 3 列的组合进行散列,而不是简单地散列一个整数。
  • 它们导致错误:开发人员不可避免地错误地实现了主键类的 equals 和 hashCode 方法。或者他们让它可变,一旦存储在 HashSet 或 HashMap 中就修改它们的值
  • 他们污染了模式。如果另一个表需要引用这个 3 列的表,它需要有 3 列而不是只有一个作为外键。现在假设你按照同样的设计,让这个 3 列外键成为这个新表的主键的一部分,你很快就会有一个 4 列的主键,然后在下一个表中有一个 5 列的 PK,等等. 等,导致数据重复和脏模式。

除了其他三列之外,另一种方法是使用单列自动生成的主键。如果要使三列的元组唯一,请使用唯一约束。

于 2013-01-01T18:11:16.813 回答
41

即使现在 - 也许 - 回答你的问题为时已晚,我想在这里就 Hibernate 使用代理键的需要(这真的是一个建议吗?)提出另一种观点(我希望更温和)。

首先,我想明确一个事实,即代理键(人工自动生成的)和自然键(由具有域含义的列组成)各有利弊。我并不是说一种键类型比另一种更好。我想说的是,根据您的要求,自然键可能是比代理键更好的选择,反之亦然。

自然键的神话

  1. 复合键的效率低于代理键。不!这取决于使用的数据库引擎:
  2. 自然键在现实生活中不存在。抱歉,它们确实存在!例如,在航空业中,以下元组对于给定的预定航班(航空公司、出发日期、航班号、操作后缀)将始终是唯一的。更一般地,当一组业务数据通过给定标准保证是唯一的时,那么这组数据就是一个[好的]自然关键候选者。
  3. 自然键“污染”子表的架构。对我来说,这更像是一种感觉,而不是一个真正的问题。拥有 4 列 2 字节的主键可能比 11 字节的单列更有效。此外,这 4 列可用于直接查询子表(通过使用 where 子句中的 4 列)而不连接父表。

代理键的缺点

代理键是:

  1. 性能问题的来源:
    • 它们通常使用自动递增的列来实现,这意味着:
      • 每次您想获得一个新的 Id 时都要往返于数据库(我知道这可以使用缓存或 [seq]hilo 类似的算法来改进,但这些方法仍然有其自身的缺点)。
      • 如果有一天您需要将数据从一个模式移动到另一个模式(至少在我的公司经常发生这种情况),那么您可能会遇到 Id 冲突问题。是的,我知道你可以使用 UUID,但最后一个需要 32 个十六进制数字!(如果您关心数据库大小,那么这可能是一个问题)。
      • 如果您对所有代理键使用一个序列,那么 - 可以肯定的是 - 您最终会在数据库上发生争用。
  2. 容易出错。序列具有最大值限制,因此 - 作为开发人员 - 您必须注意以下事实:
    • 您必须循环您的序列(当达到最大值时,它会返回到 1,2,...)。
    • 如果您将序列用作数据的排序(随着时间的推移),那么您必须处理循环的情况(ID 为 1 的列可能比 ID 最大值 - 1 的行更新)。
    • 确保您的代码(甚至是不应发生的客户端接口,因为它应该是内部 Id)支持用于存储序列值的 32b/64b 整数。
  3. 他们不保证非重复数据。您始终可以有 2 行具有所有相同的列值但具有不同的生成值。对我来说,从数据库设计的角度来看,这是代理键的问题
  4. 更多在维基百科...

为什么 Hibernate 喜欢/需要代理键?

正如Java Persistence with Hibernate参考中所述:

更有经验的 Hibernate 用户只使用 saveOrUpdate();让 Hibernate 决定什么是新的和什么是旧的要容易得多,尤其是在具有混合状态的更复杂的对象网络中。独占 saveOrUpdate() 唯一(不是很严重)的缺点是它有时无法在不触发数据库的 SELECT 的情况下猜测一个实例是旧的还是新的——例如,当一个类被映射到一个自然复合键并且没有版本或时间戳属性。

可以在此处找到限制的一些表现形式(我认为,我们应该这样称呼它) 。

结论

请不要太拘泥于你的观点。在相关时使用自然键,并在最好使用代理键时使用它们。

希望这对某人有所帮助!

于 2013-12-27T15:56:48.610 回答
10

我会从设计的角度考虑这个问题。不仅仅是 Hibernate 认为它们是好是坏。真正的问题是:自然键是否适合成为我的数据的良好标识符?

在您的业务模型中,今天可以很方便地通过一些数据来识别记录,但业务模型会随着时间的推移而发展。当这种情况发生时,您会发现您的自然密钥不再适合唯一标识您的数据。并且由于其他表中的参照完整性,这将使事情更难改变。

拥有代理 PK 很方便,因为它不会将您的数据在存储中的识别方式您的业务模型结构联系起来。

自然键无法从序列中生成,数据无法通过其数据识别的情况更为常见。这是自然密钥与存储密钥不同的证据,它们不能被视为通用(和好的)方法。

使用代理键简化了应用程序和数据库的设计。它们更容易使用,性能更高,并且做得很好。

自然键只带来缺点:我想不出使用自然键的单一优点。

也就是说,我认为 hibernate 对自然(组合)键没有真正的问题。但是有时您可能会发现一些问题(或错误),以及文档问题或寻求帮助,因为 hibernate 社区广泛承认代理键的好处。因此,请为您选择组合键的原因准备一个很好的答案。

于 2013-01-01T18:29:54.040 回答
1

如果正确理解 Hibernate 文档:

“有一个替代<composite-id>声明允许使用复合键访问遗留数据。强烈建议不要将其用于其他任何事情。”

关于主题 5.1.4。id 标记 xml<id>使主键映射过早我们可以得出结论,hibernate 文档不鼓励使用复合主键映射而<composite-id>不是<id>xml 标记,并且不会使任何引用否定使用复合主键。

于 2016-10-06T23:16:12.167 回答
0

使用数据库作为工具开发的应用程序肯定更有利于保持代理键上的工作流,使用聚集索引进行查询优化。

然而,需要特别注意数据仓库和 OLAP 风格的系统,它们利用大量的事实表将维度的代理键联系在一起。在这种情况下,数据决定了可用于维护记录的仪表板/应用程序。

因此,与其说一种方法优于另一种方法,不如说是一种指令对另一种方法有利,用于关键构造:您不会很容易地开发 Hibernate 应用程序来利用对 SSAS 系统实例的直接访问。

我使用这两种键组合进行开发,并且感觉要实现实心星形或雪花模式,具有聚集索引的代理通常是我的首选。

因此,对于 OP 和其他人的看法:如果您想保持 db 与您的开发(Hibernate 擅长)保持不变 - 使用代理方法,并且当数据读取趋于缓慢时,或者您注意到某些查询耗尽性能,恢复到您的特定数据库,并添加优化查询顺序的复合聚集索引。

于 2014-11-26T15:49:57.717 回答
0

不要混淆主键和唯一索引。如果您使用自然键,则将您的键链接到您的业务、业务数据;它不是那么好。因此,即使可以使用一组数据来定义复合键,也不建议这样做。在我看来,复合键主要在您拥有现有模式时可用

于 2021-02-01T12:44:12.637 回答