使用数据映射器模式处理关系

10

我正在使用数据映射器模式,并且想知道在该模式下处理关系的最佳方法。我花了很多时间在谷歌和Stack Overflow上搜索解决方案,我找到了一些,但我对它们仍不完全满意,特别是在一个我将尝试解释的特殊情况下。

我正在使用PHP,所以我将放置 PHP 代码示例。

假设有一个表"team"(id, name)和一个表"player"(id, name, team_id),它们之间存在1-N关系。通过实现数据映射器模式,我们可以拥有以下类: Team、TeamMapper、Player 和 PlayerMapper。

到目前为止,一切都很简单。如果我们想从一个团队获取所有球员怎么办?

我找到的第一个解决方案是在 Team 类中创建一个 getAllPlayers() 方法,使用延迟加载和代理来处理这个问题。然后,我们可以像这样检索一个团队的球员:

$players = $team->getAllPlayers();

我发现的第二种解决方案是直接使用PlayerMapper并将团队ID作为参数传递。像这样:

$playerMapper->findAll(array('team_id' => $team->getId()));

现在,假设我想展示一个带有'Players'列的HTML表格,其中包含所有团队的球员。如果我们使用我描述的第一种解决方案,我们将不得不执行一次SQL查询来获取团队列表,并且对于每个团队还需要执行一次查询以获取球员,这意味着需要进行N+1次SQL查询,其中N是团队数。

如果我们使用我描述的第二种解决方案,我们可以先检索所有团队ID,将它们放入数组中,然后将其传递给player mapper的findAll方法,类似于这样:

$playerMapper->findAll(array('team_id' => $teamIds));

在这种情况下,我们只需要运行2个查询。好多了。但我仍然对这个解决方案不太满意,因为关系没有描述到模型中,而且是开发人员必须了解它们。

所以我的问题是:数据映射器模式是否有其他选择?在我给出的示例中,是否有一种良好的方式来选择所有具有模型中关系描述的所有球队和球员,只需使用2个查询?

提前谢谢!

2个回答

2
如果您查看Martin Fowler描述DataMapper如何工作的文本,您会发现可以使用一个查询来获取所需的所有数据,然后将该数据传递给每个映射器,使映射器仅选择其需要的数据。
对于您而言,这将是一个从Team到Player连接的查询,返回一个结果集,其中每个唯一的Player都有重复的Team数据。
然后,您必须在映射代码中考虑重复,只有在数据更改时才创建新对象。
我做过类似的事情,相当于Team映射器迭代结果集,并为每个唯一的团队将结果集传递给Player映射器,以便它可以创建球员,然后将球员添加到团队的集合中。
虽然这样做可以起作用,但此方法在下游存在问题...

@Vincent:你似乎还没有完全被说服,但是没有说明原因——如果不接受答案,这个评论对未来寻求帮助的人来说是没有用的。 - Zayne S Halsall
@DavidOsborne:下游还有哪些问题? - Zayne S Halsall
我在将子对象映射为父对象的一部分时引入了一个缺陷。这很好,直到你有双向映射并且创建了一个无限循环!我认为像NH这样的ORM通过执行映射然后链接相关对象来解决这个问题。 - David Osborne
我并不太信服,主要是因为使用一个大查询来获取关系对我来说不像是一个干净和优化的解决方案(我们会得到重复的数据)。如果“team”表只与“player”表有关系,那么这可能还可以接受,但如果“team”表与其他表有更多的关系,那么我们就会得到越来越多的重复数据和一个沉重的SQL查询。此外,我认为所提出的解决方案在几句话中解释起来可能很容易,但实际实现非常复杂,并且会带来进一步的问题。 - Vincent
这就是为什么没有人编写自己的ORM!http://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science/ - David Osborne
显示剩余3条评论

0

我有一个可能的解决方案,已经在我的一个项目中成功实现。它并不复杂,在上述示例中只使用了2个查询。

解决方案是添加另一层代码来处理关系。

例如,我们可以将它放在一个服务类中(也可以用于其他事情,而不仅仅是处理关系)。 假设我们有一个名为TeamService的类,它位于Team和TeamMapper之上。 TeamService将具有一个getTeamsWithRelationships()方法,该方法将返回一个Team对象数组。 getTeamsWithRelationships()将使用TeamMapper获取团队列表。然后,使用PlayerMapper,它将仅使用一个查询获取这些团队的球员列表,并通过使用Team类的setPlayers()方法将球员设置为团队。

这个解决方案非常简单易行,并且适用于所有类型的数据库关系。 我猜有些人可能会反对它。如果是这样,我很想知道问题是什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接