为啥在休眠中不鼓励复合键?

Posted

技术标签:

【中文标题】为啥在休眠中不鼓励复合键?【英文标题】:Why are composite keys discouraged in hibernate?为什么在休眠中不鼓励复合键? 【发布时间】:2012-12-16 06:38:07 【问题描述】:

这是来自Hibernate official tutorial:

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

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

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

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

【问题讨论】:

您应该定义一个代理主键,然后将您现有的组合重新定义为唯一(自然或业务)键。这会带来更简洁、更易于维护的数据库设计。 【参考方案1】:

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

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

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

【讨论】:

但是在使用中,您必须拥有这 3 个参数才能获得唯一的参数 - dbs 也经常使用 btrees 而不是哈希,因此访问密钥的成本不会少,并且拥有单独的唯一 ID 是一种开销。在设计中,喜欢生成 id 的人和需要物理密钥的人之间在设计上存在未解决的争论,这会导致复合密钥。 单个数字列上的索引比 3 列上的索引更快,占用的空间更少。这就是应用程序中使用最多的索引。另请参阅我在答案中添加的附加点。我见过人们使用这些类型的复合 PK,并导致具有 9 列主键的表只是对另一个表的引用,以及 1 或 2 个功能列。太可怕了。 我本来打算对此 +1,但后来你添加了最后一个要点,现在我想 +10。任何遇到过“级联复合键”这一可怕问题的人都会真正开始将简单的代理键视为数据库设计的重要组成部分。 简单的多对多链接表呢?他们没有大多数这些问题,在数据库级别上解决这些问题的明显方法是拥有所有字段的复合主键。 如果您面临上述任何原因,那么您需要重新考虑您的设计。否则,您已经使用复合键做出了正确的决定。关于 Hibernate 的限制,请查看 mwsiri 的答案。【参考方案2】:

我会从设计的角度考虑这个问题。不仅仅是 Hibernate 认为它们是好是坏。真正的问题是:自然键是否可以很好地识别我的数据?

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

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

自然键不能从一个序列中生成,并且不能通过其数据识别的数据的情况要多更多。这是自然密钥与存储密钥不同的证据,不能将它们视为通用(和良好)的方法。

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

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

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

【讨论】:

+1,对于受应用程序开发人员控制而不是与业务绑定或由外部实体(例如 ssns 或 abas)控制的标识符,还有一些话要说。 【参考方案3】:

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

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

关于自然键的神话

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

代理键的缺点

代理键是:

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

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

Java Persistence with Hibernate 参考中所述:

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

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

结论

请不要太拘泥于你的观点。在相关时使用自然键,在更适合使用时使用代理键。

希望这对某人有所帮助!

【讨论】:

我喜欢在适当、响亮和自豪的地方使用复合键!它使设计更加简洁。当它是适当的地方时,您不会面临被告知的任何缺点。 您的回答中的一个重要声明是,More generally, when a set of business data is guaranteed to be unique by a given standard then this set of data is a [good] natural key candidate. 通常,最终执行该标准的不是技术人员。通常,它的一些非技术经理要求在 pk 中添加一个额外的密钥,同时骑着一些史蒂夫乔布斯,比如电源跳闸/妄想,以应对业务中的异常情况。你自己的例子可能暗示了这一点。为什么航空公司、departmentDate 和 flightNumber 不是唯一的?为什么甚至需要后缀? 关于“为什么还需要后缀?”;每日例行航班延误至次日(即原定23:00后改00:30)的具体情况需要。如果我们只使用departmentDate 和flightNumber,那么我们将有一个重复的航班(原定于第n+1 天的航班和原定于第n 天但由于延误而重新安排到第n+1 天的航班)。为了保持相同的航班号以避免客户混淆,我们只添加了一个后缀(例如“D”)。 确实有时——不幸的是——非技术人员最终会为技术人员制定标准。但是,我认为标准应该由技术人员和职能人员共同制定。后缀示例很好地证明了在定义标准时必须考虑一些非技术因素(人们因更改航班号而迷路)。此外,当一个标准已经被广泛采用时,它是“好”还是“坏”标准不再重要。 中肯的建议:“请不要过于拘泥于自己的观点。在相关的情况下使用自然键,在更好的情况下使用代理键。”【参考方案4】:

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

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

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

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

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

【讨论】:

【参考方案5】:

如果正确理解 Hibernate 文档:

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

关于主题 5.1.4。 id tag xml <id> 启用主键映射太快我们可以得出结论,hibernate 文档不鼓励使用 <composite-id> 而不是 <id> xml 标签用于复合主键映射和 NOT将任何引用设为否定以使用复合主键。

【讨论】:

给定问题已经有了答案,如果您对如何改进它有任何想法。请将您的suggestion 发布到所选答案以提供帮助和更新。 我的意图是否认hibernate文档不建议使用主键组合的引用并将讨论引导到一个不是基于错误引用的字符,因为谁反对文档的意见?我不反对使用主键组合,但我反对理解的不好的部分引用【参考方案6】:

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

【讨论】:

以上是关于为啥在休眠中不鼓励复合键?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用注释创建休眠复合键

使用复合键(长,日期)选择最新的对象的休眠查询

没有复合键的休眠中的多对多

休眠 - 使用包含父 ID 的复合键 - OneToMany

休眠仅保存具有复合主键中的父外键的子表条目

事务在休眠中不回滚