21

我几乎完成了我的数据映射器,但现在我正处于关系方面。

我将在这里尝试说明我的想法。我无法找到关于这个主题的好文章/信息,所以也许我正在重新发明轮子(当然,我可以使用一个大框架 - 但我想通过这样做来学习)。

1:1 关系

首先,让我们看一下 1:1 的关系。通常,当我们有一个名为“Company”的域类和一个名为“Address”的域类时,我们的 Company 类将具有类似 address_id 的内容。假设在大多数情况下,我们只显示公司列表,并且只有在有人查看详细信息时才需要地址。在这种情况下,我的数据映射器 (CompanyDataMapper) 只是延迟加载,这意味着它只会从数据库中获取 address_id,但也不会执行连接来获取地址数据。

一般来说,我对每个关系都有一个 getter 方法。所以在这种情况下,有一个 getAddress(Company companyObject) 方法。它接受一个公司对象,查找它的地址属性,如果它为 NULL,则使用该地址对象 (AddressDataMapper) 的 Mapper 类从数据库中获取相应的地址对象,并将该地址对象分配给指定的地址属性公司对象。

重要提示:是否允许数据映射器使用另一个数据映射器?

假设在大多数情况下,您需要公司对象和地址对象,因为您总是将它们一起显示在一个列表中。在这种情况下,CompanyDataMapper 不仅会获取公司对象,还会使用 JOIN 进行 SQL 查询以获取地址对象的所有字段。最后,它遍历记录集并为新对象提供相应的值,将地址对象分配给公司对象。

听起来很简单,到目前为止。

1:n 关系

这些怎么样?与 1:1 的唯一区别是,一个 Company 可能有多个 Address 对象。让我们看一下:当我们大部分时间只对公司感兴趣时,数据映射器只会将公司对象的地址属性设置为 NULL。address 属性是一个数组,它可以引用无、一个或多个地址。但我们还不知道,因为我们延迟加载,所以它只是 NULL。但是,如果我们在大多数情况下也需要所有地址呢?如果我们要显示一个包含所有公司及其所有地址的大列表?在这种情况下,事情开始变得非常丑陋。首先,我们不能为每个地址对象加入地址表五十次(我坚信这是不可能的,如果是,性能将低于零)。所以,当我们进一步思考这个问题时,它'

重要提示:这是真的吗?如果我有 10 家公司,每 10 个地址,我必须发送 100 个查询以获得 100 个地址对象吗?

m:n 关系

假设一个地址对象只包含国家、州、城市、道路和门牌号码。但一所房子可能是一座大型商业大厦,里面有很多公司。就像那些现代办公楼之一,任何人都可以租一个小ROM在其网站上炫耀这座塔。所以:许多公司可以共享同一个地址。

我还没有计划处理这种问题。

重要提示:可能这不是比 1:n 关系更大的问题吗?

如果有人知道一个很好的资源来详细介绍解决/实现这个问题,我会很高兴有一个链接!

4

3 回答 3

17

在我开始之前,我假设您已经从头到尾阅读了 Fowler 的 PoEAA 书。=) 另外,我认为您已经想到了在处理 ORM 时遇到的第一个初始问题。我可以突出显示一个简单的方法,例如使用相同的标识符多次调用 DataMapper 并始终返回相同的对象(读取为 IdentityMap)。

重要提示:是否允许数据映射器使用另一个数据映射器?

如果第二个是第二个弱引用,则只能让一个 DataMapper 访问另一个 DataMapper。

假设在大多数情况下,您需要公司对象和地址对象,因为您总是将它们一起显示在一个列表中。在这种情况下,CompanyDataMapper 不仅会获取公司对象,还会使用 JOIN 进行 SQL 查询以获取地址对象的所有字段。最后,它遍历记录集并为新对象提供相应的值,将地址对象分配给公司对象。

您在这里尝试讨论的问题在实践中听起来很简单,但在幕后有点复杂。

首先,您不应该拥有 getAddress(Company),而是从拥有 Proxy 对象中受益。代理是给定实例的未初始化表示。在这种情况下,代理包含对您正在查找的条目的引用。它必须从您的原始对象扩展而来,并且需要提供一个初始化方法,以及一个相关的 DataMapper 来加载它。

关于一次加入和加载多个对象的第二部分称为 Hydrator。Hydrators 接收线和列的平面结构并转换为对象图。但它确实涉及到一个单独的问题:如果您只是在处理对象,为什么要获取表?尝试采用对象获取方法将导致您实现一种 OQL(对象查询语言)。

重要提示:这是真的吗?如果我有 10 家公司,每 10 个地址,我必须发送 100 个查询以获得 100 个地址对象吗?

在 PHP 中处理对象集合是一场噩梦。是的,由于缺乏强大的集合实现,该语言很糟糕。基本上,您需要在这里处理不同的情况: - 新实例和此元素列表中的所有元素都是新的 - 新实例和此元素列表中的所有元素都是预先存在的 - 新实例和此元素列表中的元素混合在新的和预先存在的 - 预先存在的实例并且不触及元素列表中的任何东西 - 预先存在的实例和操作列表中的项目

我在这里非常简单,但我想强调的主要一点是需要一个 Collection 对象。其中有两个:一个处理新列表,一个处理现有列表。一旦您尝试访问其中的任何内容,处理现有列表的列表就需要能够加载该集合。这是没有 n + 1 个问题的唯一方法。

在这里,它还突出了您必须处理的下一个大问题。关联可以是单向的或双向的。这意味着公司知道地址但地址不知道公司是单向的,而用户是许多组的一部分,并且包含许多用户的组是双向关联的。事情在这里很容易变成一场噩梦,这就是为什么您需要映射模式来正确理解正在发生的事情。

处理多对多与处理集合是一样的。

有一个重要的部分你还没有考虑。如果我构建了我的整个对象图(公司和地址)并且我决定持久化它们......它需要同时持久化,还是我必须手动告诉我想要持久化的内容?两种方式都有不同的问题。假设您想要第一种方法。您刚刚输入了我认为要实现的最复杂的设计模式之一:UnitOfWork。然后,您必须处理要应用的实体的顺序排序以不产生约束问题(阅读拓扑排序以了解如何解决此问题)。如果您采用第二种方法,您可能很容易陷入一种感觉您的工具坏了的情况,主要是因为您的对象图很容易处于不一致的状态。

最后......你打算对继承做任何支持吗?如果是积极的,你的整个计划就进入了一个全新的水平。=(试图解释会带我一本书。但我可以指出一些你可以看的设计模式:具体表继承(1个类,1个表),单表继承(N个类,1个表)和类表继承(N类,M 表)。

我可以在这里深入探讨许多不同的点,但 ORM 通常会导致头部爆炸。我先停下来。

PS:我是Doctrine ORM的核心开发者之一。除非您出于学习目的这样做,否则不要费心尝试创建另一个。这是一个极其复杂、耗时的过程,甚至在编写第一行代码之前就需要对事情的工作方式进行大量计划。事实上,我们计划 Doctrine ORM 用了 2 年时间,用了 1 年时间来可靠地实现核心功能。我不是在劝阻你,但正如 Fowler 在他的 ORM 仇恨文章中所说,对于一个更复杂的问题,它是一个复杂的解决方案。

于 2014-01-15T14:29:31.943 回答
3

我期待您在这个话题上得到任何答案,但与此同时,为什么不直接跳到亚马逊(或您当地的书商)并最终购买

这些书包含您在各种问题中被指出的原始模式,并且被认为是设计模式和软件架构中的参考作品。

于 2009-12-28T22:31:26.953 回答
0

我也在解决这个问题。首先,我改编了 Matt Zandstra 的PHP Objects, Patterns, and Practice (2d Ed) 中的 Data Mapper 模式。我现在看到新版本出来了

也许设置中最巧妙的部分是“集合”对象。我不确定你使用的是什么语言,所以我会省略你的细节。可以这么说,PHP 有一个 Iterator 接口,它可以首先加载一个数组(地图,用其他语言),然后在循环时动态地将原始数据转换为对象(水合物?)。

和你一样,我也在为如何加载关系而苦苦挣扎。到目前为止,我发现我可以在 Mapper 类中编写我的大量 JOIN 查询,并为目标对象创建一个脱水集合,并同时潜入相关对象的数据。

我真的不喜欢“延迟加载”,因为它会导致很多数据库查询。知道我正在使用数十或数百个查询来完成可以一次完成,这冒犯了我的完美主义情感。

我也很期待更多的答案。

于 2010-07-02T13:23:22.120 回答