【问题标题】:Best design pattern for database table joins数据库表连接的最佳设计模式
【发布时间】:2009-10-23 11:04:37
【问题描述】:

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

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

其中一项功能可以是支票簿,也可以是一些高级产品,例如免费旅行保险。

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

似乎不适合使用 ActiveRecord 或数据访问对象,因为它们通常只关注每个表一个类;数据映射器也是如此。我意识到我可以将其分解为每个表的操作,例如getAccountsForFeature() 和 getCustomersForAccount(),但我想一键检索。

如果我们要“弯曲”每个表一个类的模式并使用数据访问对象模式,比如说,getCustomersForFeature() 方法应该继续使用 CustomerDAO 还是 FeatureDAO?但这对我来说感觉不对,因为你会用其他表的知识污染你的 DAO。

请提出建议。

【问题讨论】:

  • 我已经为这个问题添加了一个社区 Wiki 答案,它根据此处的答案和附加阅读总结了我认为的可用方法。这丝毫没有减损这里的其他答案,但这是我一直在寻找的面向模式的方法,其中包括聚合的想法。我犹豫选择它作为“接受的答案”(我可以对自己的答案这样做吗?),直到它受到更多审查。

标签: design-patterns database-design join


【解决方案1】:

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

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

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

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


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

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

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

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

【讨论】:

  • 有道理。那么在我给出的示例中,您是否建议只创建一个 CustomerModel?
【解决方案2】:

我花了更多时间阅读该主题(包括比尔回答中引用的领域驱动设计迷你书)。那本书中有一个聚合的概念,它最接近地代表了我想要实现的目标;客户封装并控制对帐户和功能的访问。如果我们坚持领域驱动设计方法,我们可以使用 Repository 来控制客户的检索,在我看来,这就是数据库结构知识的封装。

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

感谢大家的贡献。我投票赞成促成这一结论的答案。由于这可能不是本次讨论的最后一点,我已将此答案标记为 Community Wiki。

【讨论】:

    【解决方案3】:

    Rails 中的 Active Record 对这些进行建模的方式是允许 Customer 对象拥有_many Accounts - 这基本上转化为 Account 对象的集合,而这些对象又具有 Features 的集合。这些关系可以是双向的,因此每个 AR 模型都可以根据您的需要“了解”它的关系。

    我认为对象了解其他表是很好的,因为这种关系是 OO 和 RDBMS/SQL 的基础。

    【讨论】:

    • 此外,您还可以使用 has_many :users, :through => 帐户定义功能。
    • 您能否详细说明映射是如何在幕后完成的,例如检索客户会导致自动检索关联的帐户和功能(即跨 3 个表的连接)还是这些是延迟加载的?如果我想检索 Customer/Account/Feature 的 一些 属性(即子集)而不是整个对象图,我该怎么做?
    • Rails 中的 Active Record 非常复杂。默认行为是集合将被延迟加载,这可能意味着额外的查询。但是,可以通过在初始查找时使用“包含”机制来预先加载数据(因此,当您加载客户时,您告诉它也加载帐户,AR 将为您创建一个连接)。还可以加载特定字段 - 您只需告诉它要加载哪些字段。
    【解决方案4】:

    在 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 都应该具有非常相似的行为,即使语法和结构会有所不同。

    【讨论】:

      【解决方案5】:

      通常,根据您使用的 SQL 数据库的功能对数据进行建模。忽略 ORM、映射器等。

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

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

      【讨论】:

      • 我对忽略 ORM 等持谨慎态度,因为我想在尽可能少的地方(最好是一个)封装有关如何映射到/从数据库映射的知识。噩梦般的场景是更改数据库结构,然后必须找到所有受影响的查询。您对 ActiveRecord / DAO 施加了哪些限制(如果有),例如每桌一节课?
      • 完全可以在没有任何 ORM 的帮助下进行数据库迁移,只需使用纯 SQL 脚本。这通常在具有训练有素的 DBA 的大型数据库安装中完成。如果您打算使用 SQL 数据库,则必须分道扬镳,并在中间遇到 DBA。有时,采用他们的做法。 ORM 适用于特定于应用程序的数据库中的简单数据模型,但许多应用程序并不拥有数据库,它们只是几个用户之一。
      【解决方案6】:

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

      select c from Customer c left join c.accounts a left join a.features f where f.name = 'cool'

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

          虽然对象到现实的东西非常适合 CRUD 类型的表维护,但我发现在复杂查询表方面没有什么比实际 SQL 更好的了。

          ORM 模型只是一个过于抽象的抽象。

          【讨论】:

          • 是和不是。是的,因为在某些情况下不能使用 ORM API 来表达查询,并且不,因为如果你有一个强大的 ORM(Hibernate / NHibernate)并且足够了解它的 API,这些情况很少。如果你只将 ORM 用于最简单的 CRUD 操作,我认为你会失去 ORM 提供的大部分优势。
          【解决方案9】:

          需求通常由您决定。在您的截止日期、您的工作文化或您的项目要求之前。您的截止日期可能会显示“代码到工具”并削减功能。您的员工可能需要“没有新的库或故障模式”。在我自己的环境中,你会听到我讲述这个故事:

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

          SQL 视图可能提供了一种从对象中抽象出连接的好方法,并且可以让您更独立于应用程序代码来更改架构。然而,更新视图非常棘手,具体取决于您的数据库。如果 DB 可移植性很重要,那可能会对您的设计产生很大影响。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-02
            • 2023-03-12
            • 2012-05-28
            • 1970-01-01
            • 2011-07-01
            相关资源
            最近更新 更多