1

假设我有以下课程:

User(域对象) UserMapper(数据映射器) Achievement(域对象) AchievementCollection(域对象) AchievementMapper(数据映射器)

这里的关系是 aUser具有 aAchievementCollection作为属性(或者, aUser具有 many Achievements)。

我想要做的是查询用户列表,并查询他们的所有成就。但是,我不完全确定如何使用数据映射器模式来做到这一点。如果我遍历每个用户并使用AchievementMapper 来查询用户的成就,那将是相当低效的,特别是如果我有50 多个用户要查询。

处理这种情况的最佳方法是什么?(另外,这是出于学术/学习目的,这就是我不使用 Doctrine 的原因)

UserMapper 是否应该负责获取用户的成就?我应该只使用成就映射器按用户 ID 查询所有成就吗?(并遍历每个用户?)

4

1 回答 1

4

使用ORM(即使是您自己编写的,所以任何将对象映射到关系表或反之亦然的东西)始终是一种权衡:当然有优点,但也有缺点。

ORM 和写自定义查询之间的主要权衡是易用性和开发速度与效率。换句话说,优势是,例如,您突然可以编写$userMapper->getUserById(5);并获得相关User对象作为回报。查询的组合和执行、结果集的获取以及对象上的映射都为您完成。缺点是,除了最基本的用例之外,ORM 不会执行最优(组合)查询来实现目标。作为牺牲这一点的回报,您将获得更多易用性(作为程序员)和更快的开发(尽管 ORM 也可以阻止您......)。

通常,在使用 ORM 时,您(尝试)忘记较小的低效率。如果您尝试创建一个与您在其位置编写的自定义 SQL 查询一样高效的 ORM,您最终将重新发明 SQL。

在您只有 50 个用户的示例中,我将只使用 ORM 并忍受为每个用户查询成就表一次的低效率。任何体面的数据库服务器都不会有任何问题。

尽管如此,既然您提到了学术方面,我已经提出了一些改进每次调用查询场景的选项。对于您的特定用例,您可以尝试某种形式的缓存:预取所有Achievement对象,将该集合标记为“完成”(即映射器可以假设Achievement数据库中没有其他尚未加载的 s),然后$user->getAchievements()检查是否有“完整”的成就集合,如果有,请使用它而不是数据库。从外部的角度来看,这可能看起来像这样:

$achievementMapper->preloadAll();
foreach ($userMapper->getAll() as $user) {
    echo "User {$user->getName()} has the following achievements: ";
    foreach ($user->getAchievements() as $achievement) {
        echo $achievement->getName();
    }
}

在内部,理想情况下这只执行两个查询:一个选择所有成就,一个选择所有用户。用户和成就的加入是由内存中的 ORM 完成的。这种方法的主要缺点是高内存使用,当您对某种类型的所有对象不感兴趣时​​(例如,只有拥有超过五个成就的用户),它的效率非常低。

一个更复杂的选项是根据您选择的用户预加载成就,可能看起来像这样:

$top100Users = $userMapper->getTop100();

// internally cache all Achievement objects linked to any of the 100 users
$achievementMapper->preloadByUserCollection($top100Users) 

使用这种方法,可能的低效率会降低,但是(记住,总是需要权衡)ORM 的使用和 ORM 本身都会变得更加复杂。例如,成就映射器必须记住它为哪个用户预加载了所有成就,如果不是这种情况(或者数据库从那时起发生了变化),它仍然会转到数据库。

另一个(更复杂的)选项是像Doctrine 的 DQL fetch joins这样的机制,您可以在其中告诉 ORM 它应该从查询的结果集中“映射”哪些不同的对象(类型)。ORM 中的“not-have-to-write-queries”部分因此而消失了一点,但结果集到具有正确关联的对象的自动映射仍然存在。

于 2013-10-29T00:32:29.830 回答