数据库表连接的最佳设计模式

时间:2009-10-23 11:04:38

标签: design-patterns database-design join

我正在研究我的应用程序的模型层的编程/设计模式,并且我想知道哪一个最适合您正在进行涉及跨多个表的连接的检索的情况。

例如,假设您有以下表/关系:客户 - > 1..n帐户 - > 0..n功能

其中,功能可以是支票簿,也可以是免费旅行保险等优质产品。

然后我想做getCustomersForFeature()来检索所有拥有免费旅游保险账户的客户。

使用ActiveRecord或数据访问对象似乎不合适,因为这些通常关注每个表的一个类;同样适用于Data Mapper。我意识到我可以将其分解为每个表的操作,例如getAccountsForFeature()和getCustomersForAccount(),但我想在一次点击中进行检索。

如果我们要“弯曲”每个表的一类模式并使用数据访问对象模式,比如说,getCustomersForFeature()方法是否会继续使用CustomerDAO或FeatureDAO?但这对我来说并不合适,因为你会在了解其他表格的情况下污染你的DAO。

建议请。

9 个答案:

答案 0 :(得分:9)

Model-Driven Design中,您的应用程序中有逻辑实体,这些实体可以映射到物理数据库中的多个表。当然,您需要运行更复杂的SQL来获取完整的实体,而Model类是实现这些关系的地方。

我认为ActiveRecord及其同类产品可以用于针对单个表的简单查询,但是尝试强制将这些模式用于复杂查询是非常困难的。幸运的是,我们已经有了一种简洁的,特定于域的语言,您可以使用它来指定复杂的查询:SQL。

因此,在Model类中,您可以使用方法来执行逻辑应用程序级任务,例如getCustomersForFeature()。在该方法的代码中,您应该使用ActiveRecord方法或直接SQL(如果需要)编写特定查询。因此,数据库的具体设计封装在Model类中的一个位置。

这意味着我们需要打破Model和Table之间的耦合。 Model和ActiveRecord类之间的OO关系不是 IS-A - 它是 HAS-A (或者有很多)。


重新评论:那么我们的模特是什么?如果您的应用程序主要需要作为一个实体与客户合作,并将功能视为或多或少是客户的属性,那么您的模型将是客户,并且它将隐藏功能存储在数据库中的单独表中的事实。 Customers模型将在内部使用ActiveRecord或纯SQL来收集所需的数据,以提供具有关联的多值属性的客户的复杂对象的完整视图。

但是,如果您的应用程序还需要直接使用功能,该怎么办?例如,管理员的屏幕,您可以在其中根据功能获取报告或创建新功能。然后通过Customer模型访问功能将是笨拙的。所以你毕竟需要一个功能模型。只有它可以使用不同的方法来实现您需要对功能执行的操作。

每个模型类都应公开一个API,其中只包含您使用该模型所需的内容。甚至不需要对称。仅仅因为您的客户模型可以获取具有给定功能的所有客户,并不一定意味着您的功能模型需要获取给定客户的所有功能。遵循YAGNI规则。

但是,在创建了Customer模型和Features模型之后,这是否会导致重复知道表之间关系的逻辑?是的,它可以。这是object-relational impedance mismatch范围内的众多问题之一。

答案 1 :(得分:3)

我花了一些时间阅读这个主题(包括Bill的答案中提到的Domain Driven Design迷你书)。该书中有一个 Aggregate 概念,它最能代表我想要实现的目标;客户封装并控制对帐户和功能的访问。如果我们坚持使用域驱动设计方法,我们可以使用存储库来控制客户的检索,在我看来,这就是封装数据库结构知识的地方。

我还看了一下我的企业应用程序架构模式副本,虽然看起来ActiveRecord模式用于直接映射类到数据库表,但如果你选择不遵循DDD的存储库方法那么 Data Mapper 适用于复合映射。

感谢所有人的贡献。我已经对得出这个结论的答案进行了投票。由于这可能不是本次讨论的最后一点,我将此答案标记为社区Wiki。

答案 2 :(得分:2)

Rails中Active Record的模式是允许Customer对象拥有多个帐户 - 这基本上转换为一组Account对象,而这些对象又包含一组功能。关系可以是双向的,因此每个AR模型都可以根据您的需要“了解”它的关系。

我认为对象知道其他表是好的,因为这样的关系是OO和RDBMS / SQL的基础。

答案 3 :(得分:1)

在C#/ Linq to SQL中,它将类似于以下内容。 (我假设实际上有一个功能类型的查找表,因此您有一个标准的功能类型列表,然后它们与帐户的关系是分开的,因此FeatureTypeId将是您的FK值,可以从下拉列表中选择列表或其他东西。)

// or whatever data type your keys are in
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId)
{
    return from feature in dbContext.Features
           where feature.FeatureTypeId == featureTypeId
           select getCustomer(feature.Account.Customer.Id);
}

在某种程度上你的DAO / BAO必须知道你的对象之间的关系,所以这是一个祖父母关系的事实不应该太可怕。

至于它在你的BAO结构中的位置,可能无论哪种方式都可以论证。我可能把它放在客户身上,因为这最终是我要去的地方。

编辑:托比指出,这种关系是双向的;再次在Linq,你也可以走另一条路:

// or whatever data type your keys are in
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId)
{
    return from customer in dbContext.Customers
           from account in customer.Accounts
           from feature in account.Features
           where feature.FeatureTypeId == featureTypeId
           select getCustomer(customer.Id);
}

任何其他ORM应该具有非常相似的行为,即使语法和结构会有所不同。

答案 4 :(得分:0)

通常,根据您使用的SQL数据库的功能对数据建模。忽略ORM,Mappers等。

如果你有一个好的数据模型,如果ActiveRecord或DAO不会以你想要的方式访问它,那么可以通过编写SQL来绕过它们。

对象关系映射应该使数据库编程更容易,但由于模型之间的差异,有时简单的方法是直接使用SQL。

答案 5 :(得分:0)

同意GalacticCowboy,ORM允许将数据库类映射细节与面向对象的查询分开。只想为java hibernate orm提供一个示例 - 有很多方法可以定义association mappings,并且遵循面向对象的HQL查询可以在所有这些方法之上正常工作

从客户c中选择c左连接c.accounts左连接a.features f其中f.name ='cool'

请注意,'Customer'是此处的类名称,'c.accounts',' a.features' 'f.name'是类级别的属性名称,即此处未提及特定于数据库的详细信息。

答案 6 :(得分:0)

我认为这是一个经典问题(至少对我而言),您希望在应用程序中表示您的数据模型。根据我自己的经验,总有一种像你所描述的那种情况。 我的解决方案与上面的一些答案非常相似。创建与您的表一对一的简单类,如果该表与另一个表有关系,那么在您的类中作为属性。

关于你的说法'我想一次性检索。',我认为你应该编写自定义SQL查询或者使用LINQ。

答案 7 :(得分:0)

虽然对于CRUD类型的表维护而言,Object to realtional stuff非常有用,但是当涉及到复杂的查询表时,我发现没有什么比实际的SQL好。

ORM模型只是一个抽象太过分了。

答案 8 :(得分:0)

要求oten为您决定。在截止日期,您的工作文化或您的项目要求。您的截止日期可能表示“工具代码”和剪切功能。您的员工可能需要“没有新的库或故障模式”。在我自己的环境中,你会听到我讲这个故事:

“......它不会引入新的ORM库,所以那些已经完成了。截止日期表明我不应该开始教自己如何制作SQL视图。我的工作文化告诉我得到它我的架构在序列化的整个表格中呻吟,我的性能要求表明我不应该缓存任何这些数据......“

SQL视图可能提供了一种从对象中抽象连接的好方法,并且可以让您更加独立于应用程序代码更改模式。根据您的数据库,更新视图非常棘手。如果数据库的可移植性很重要,那可能会给您的设计带来很多好处。

您可能会发现混合方法的和谐。如果使用表网关,则没有理由将其盲目地聚合到表的每个应用程序级视图中。为什么不使用表网关或活动记录来更新表范围中的项目,以及处理面向视图的查询的自定义类?