避免循环依赖

Posted

技术标签:

【中文标题】避免循环依赖【英文标题】:Avoid Circular Dependency 【发布时间】:2013-02-06 14:12:56 【问题描述】:

我正在开发一个旅行管理应用程序。有问题的设计如下所示:

旅行中的每个人都被指定为旅行者。 每个旅行者都有一本护照。 现在,Traveler 可以是 MainMember 或 SubMember,这取决于他是否是一家之主。 MainMember 决定诸如 TourPackage 之类的东西,他的旅行家庭的总金额等。 SubMember 在旅行时依赖于 MainMember。因此,如果 MainMember 被删除,它的所有 SubMember 也必须被删除。

所以, 旅行者有护照。 (一对一关系) Traveler 是 MainMember 或 SubMember。 (Traveler-MainMember 和 Traveler-SubMember 之间的一对零/一对) 一个 MainMember 可能有多个 SubMember。 (一对多) 一个 SubMember 只有一个 MainMember。 (多对一)

我目前的 ERD 如下所示。

如您所见,三个表 - Traveler、MainMember 和 SubMember - 形成了循环依赖关系。不过,我不确定这是否会损害我的申请。 如果我删除了一个作为 MainMember 的 Traveler,那么 1. Traveler中的一条记录被删除。 2、删除其相关的MainMember记录。 3、删除依赖于MainMember的SubMember记录。 4. 删除子会员的旅行者记录。

虽然这似乎不是问题,但作为 Traveler-MainMember 删除将始终只删除 Traveler-SubMember。不过,我对此有一种不好的预感。

谁能指导我做出更好的设计?

更新 -

在等待回复时,我根据@Daveo 的回复想出了另一个设计。基本上,Traveler 包含自引用外键。 SubMember 记录将使用它来识别他们的父母。

这是 ERD。

现在,正如@Branko 所指出的,我之前的设计中没有循环依赖的问题,我想知道哪种设计更好?

另外,哪种设计更适合通过 Hibernate 实现?我认为第二种方法在通过 Hibernate 实现时可能会导致复杂性。

对于您喜欢的设计,我还希望能提供一些关于实现模式(Hibernate 实体中的继承等)的建议。

【问题讨论】:

对 MainMember 和 SubMember 使用继承(扩展 Traveler) @guido - 这确实是我实现 POJO 实体时的想法。但是,这如何解决数据库设计中的循环依赖?或者我误解了你的说法。如果您详细说明它作为答案,我将不胜感激。 看看这里openjpa.apache.org/builds/1.0.0/apache-openjpa-1.0.0/docs/… 关于第二个设计:如果您要为 mainsub-traveler 设置单独的表,为什么不利用它并正确建模他们之间的关系,所以不能连接两个 sub 或两个 main 旅行者?从TravellerTraveller 的循环引用(这次是真实的;))不遵守这些限制。我认为@Daveo 的想法是将所有 3 个旅行者表合并为一个,这将是实现继承的“同一张表中的所有类”方法,是的,会导致很多 NULL,但也可能有一些优势(请参阅链接在我的回答中)。 “但效率很低的方法” - 在某些情况下效率较低,而在其他情况下实际上效率更高。无论哪种方式,您都会有妥协,唯一的问题是您认为在您的特定情况下更好的权衡。就个人而言,我建议您坚持原来的方法,除非您确定不这样做的具体原因。 【参考方案1】:

如您所见,三个表 - Traveler、MainMember 和 SubMember - 形成了循环依赖关系。

不,这不是循环依赖 - 此图中的任何“节点”都无法通过沿着正确方向的图“边”到达自身1。它可以更准确地描述为“合并”甚至(原型)“菱形”依赖。

不,它不会损害您的应用程序。2

您正在使用“单独表中的每个类”方法有效地实现继承(又名类别、子类化、子类型化、泛化层次结构等)。这种方法是干净的,但不能保证开箱即用的存在性3 和排他性4。 amend it 有一些方法可以做到,还有其他的继承实现策略可以,但它们有自己的pros and cons。

在我看来,您的模型非常适合您尝试完成的任务,并且在应用程序级别执行存在/排他性可能比尝试在数据库级别执行它的弊端要小。


1 “方向”是从引用到引用的表。 Traveller 既不引用 MainMember 也不引用 SubMember,因此打破了循环。

2 除非您使用的 DBMS(例如 MS SQL Server)不支持对此类“合并”依赖项的引用操作(在这种情况下,您需要实现ON CASCADE DELETE 通过触发器)。

3Traveller不能单独存在(不能是“抽象”),必须是MainMemberSubMember

4Traveller 不能同时是 MainMemberSubMember

【讨论】:

感谢您解决这个问题。甚至我也怀疑它是否是一个循环依赖(我什至在洗澡时在空气中绘制了“原始钻石形状”。;-))。您的回答已确认。 请看看我后来做的另一个设计。我想听听您对哪种设计更好以及为什么会更好的意见。【参考方案2】:

我将在数据库中只有两个表。

旅行者和密码

Traveller 将有一个 Parent_Id 字段,该字段将链接回 Traveler 表并存储主要/主要旅行者是谁。 还存储此表中主/子成员共有的字段,如联系号码

然后使用继承和 ORM 在您的实际应用程序中创建两个不同的类。 主成员和子成员 MainMember 将是 Traveler 中 Parent_Id 为空的所有行 SubMember 将是 Traveler 中 Parent_Id 不为空的所有行

【讨论】:

但是这种方法最终会导致单个 Traveler 表中出现大量空值。 MainMember 有 6 个特定特征,而 SubMember 有 2 个。因此,对于 SubMember,MainMember 列将保持为空,反之亦然。这就是我将两张表与 Traveler 分开的主要原因 - 以存储它们的特殊特征。 空值有什么问题? 好吧。让我们考虑一个 SubMember 的记录。 17 列中有 6 列(是的,Traveler 有很多属性)基本上保持为空,因为这些属性属于 MainMember。此外,在这种情况下,SubMember 感觉不合适 - 将 MainMember 的属性与它一起粘贴。这就是我担心的地方。您对此有何看法? 每种方法都有优点和缺点,取决于您的要求。例如您使用的是 ORM,其他系统会访问数据库还是仅访问这个数据库,等等。请参阅support.apple.com/kb/ta26729?viewlocale=en_us&locale=en_us 了解您可以采用的 3 种不同方法的说明。我建议“单表映射”,这似乎是开发和维护最容易/最快的 还有另一个解释它的页面在这里books.google.com.au/…

以上是关于避免循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

spring的循环依赖是什么,设计的时候怎么避免,而spring又是怎么解决的?

spring的循环依赖是什么,设计的时候怎么避免,而spring又是怎么解决的?

在 Nestjs/GraphQL 中使用接口时避免循环依赖

避免三个表之间的循环依赖

以正确的方式避免循环依赖 - NestJS

如何避免“React Hook useEffect 缺少依赖项”和无限循环