使用ORM(即使是您自己编写的,所以任何将对象映射到关系表或反之亦然的东西)总是a tradeoff:当然有优点,但也有缺点。
ORM 和写入自定义查询之间的主要权衡是易用性和开发速度与效率。换句话说,优势是,例如,您突然可以编写$userMapper->getUserById(5); 并获得相关的User 对象作为回报。查询的组合和执行、结果集的获取以及对象上的映射都为您完成。缺点是,除了最基本的用例之外,ORM 不会执行最优(组合)查询来实现目标。作为牺牲这一点的回报,您将获得更多易用性(作为程序员)和更快的开发(尽管 ORM 也会阻碍您...)。
一般来说,在使用 ORM 时,您(试图)忘记较小的低效率。如果您尝试创建一个与您在其位置编写的自定义 SQL 查询一样高效的 ORM,您最终将重新发明 SQL。
在您只有 50 个用户的示例中,我将只使用 ORM 并忍受为每个用户查询成就表一次的低效率。任何体面的数据库服务器都不会有任何问题。
不过,既然您提到了学术方面,我提出了一些改进每次调用查询方案的选项。对于您的特定用例,您可以尝试某种形式的缓存:预取所有 Achievement 对象,将该集合标记为“完整”(即映射器可以假设数据库中没有其他 Achievements尚未加载),然后让$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's DQL fetch joins 之类的机制,您可以在其中告诉 ORM 它应该从查询的结果集中“映射”哪些不同的对象(类型)。 ORM 的 not-have-to-write-queries 部分因此而消失了一点,但结果集到具有正确关联的对象的自动映射仍然存在。