数据映射器模式和关系数据

时间:2013-10-28 23:48:43

标签: php mysql datamapper

说我有以下课程:

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

这里的关系是UserAchievementCollection作为属性(或者User有很多Achievements)。

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

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

UserMapper应该负责获取用户的成就吗?我应该只使用成就映射器通过userID查询所有成就吗? (并循环遍历每个用户?)

1 个答案:

答案 0 :(得分:3)

使用ORM(即使是你自己编写的,所以通常将对象映射到关系表的任何东西,反之亦然)总是a tradeoff:当然有优点,但也有缺点。< / p>

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在内存中完成。这种方法的主要缺点是内存使用率很高,当你对某种类型的所有对象不感兴趣时​​(例如,只有五个以上成就的用户),它的效率非常低。 / p>

更复杂的选项是根据您选择的用户预加载成就,如下所示:

$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的无需编写查询部分会因此而消失,但结果集自动映射到具有正确关联的对象仍然存在。