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

Php 数据映射器模式和关系数据,php,mysql,datamapper,Php,Mysql,Datamapper,假设我有以下课程: 用户(域对象) UserMapper(数据映射器) 成就(域对象) 实现收集(域对象) AchievementMapper(数据映射器) 这里的关系是,用户将实现集合作为属性(或者,用户具有许多实现) 我希望能够查询用户列表,并查询他们的所有成就。但是,我不完全确定如何使用数据映射器模式实现这一点。如果我遍历每个用户并使用AchievementMapper查询用户的成绩,这将是相当低效的,特别是如果我有50多个用户要查询的话 处理这种情况的最佳方法是什么?(此外,这是出于学术

假设我有以下课程:

用户
(域对象)
UserMapper
(数据映射器)
成就
(域对象)
实现收集
(域对象)
AchievementMapper
(数据映射器)

这里的关系是,
用户
实现集合
作为属性(或者,
用户
具有许多
实现

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

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

UserMapper应该负责获取用户的成就吗?我是否应该仅使用成就映射器按用户ID查询所有成就?(并通过每个用户进行循环?

使用一个(即使是您自己编写的,所以任何将对象映射到关系表的通用方法,或者反之亦然)总是:当然有优点,但也有缺点

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

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

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

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

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

// internally cache all Achievement objects linked to any of the 100 users
$achievementMapper->preloadByUserCollection($top100Users) 
在内部,理想情况下这只执行两个查询:一个选择所有成就,另一个选择所有用户。用户和成果的连接由内存中的ORM完成。这种方法的主要缺点是内存使用率高,如果您对某一类型的所有对象都不感兴趣(例如,只有拥有五项以上成就的用户),那么这种方法的效率非常低

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

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

// internally cache all Achievement objects linked to any of the 100 users
$achievementMapper->preloadByUserCollection($top100Users) 
使用这种方法,可能的低效率会降低,但是(记住,始终是一种折衷)ORM的使用和ORM本身都会变得更加复杂。例如,成就映射器必须记住它为哪个用户预加载了所有成就,如果不是这样的话(或者如果数据库从那时起发生了更改),则仍然要转到数据库


另一个(更复杂的)选项是一种类似的机制,在这种机制中,您可以告诉ORM应该从查询的结果集中“映射”哪些不同的对象(类型)。这样一来,ORM中不必编写查询的部分就消失了,但结果集到具有正确关联的对象的自动映射仍然存在。

您的潜在解决方案给了我一个想法。我想在这种情况下,可以为我传入的用户集合编写自定义获取,然后使用用户ID循环执行。据我所知,在准备好的语句中循环执行与循环完整查询相比是非常快速和高效的。根据我在某些特定MySQL案例中的经验,准备好的陈述比未准备好的陈述快20-25%。尽管如此,对于重复查询的性能来说,这无疑是一个“快速的胜利”。注意,如果您正在使用PDO,请确保设置
$PDO->setAttribute(PDO::ATTR\u EMULATE\u PREPARES,false)
使PDO将语句发送到MySQL进行准备,而不是PDO在内部模拟准备(没有MySQL的参与)。出于某种原因,您的示例具有一个令人厌恶的特性,即在单个对象中结合了数据映射器和工厂。这是一个很好的例子,说明了大多数人都采用了同样的错误方法——期望同一个映射器同时处理单个实例和集合。但它们不一样。大多数用于处理集合的底层查询与用于存储/检索单个对象的查询根本不同。@tereško:当然,它们只是快速键入的示例。您可以用您更喜欢的任何名称替换
$userMapper
:-)也就是说,我相信,在一个简单的ORM中,让数据映射器也构建它映射的类的集合并不是一个很大的罪恶。