【问题标题】:Data mapper pattern, and relational data数据映射器模式和关系数据
【发布时间】:2013-11-07 22:59:39
【问题描述】:

假设我有以下课程:

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

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

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

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

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

【问题讨论】:

    标签: php mysql datamapper


    【解决方案1】:

    使用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 部分因此而消失了一点,但结果集到具有正确关联的对象的自动映射仍然存在。

    【讨论】:

    • 您的潜在解决方案给了我一个想法。我想在这种情况下,为成就编写自定义获取是可以接受的,我传入一个用户集合,然后使用用户 ID 循环执行。据我了解,与循环完整查询相比,在准备好的语句中循环执行是非常快速和高效的。
    • 根据我对某些特定 MySQL 案例的经验,准备好的语句比未准备的语句快 20-25%。尽管如此,对于重复查询的性能来说,这绝对是一个“速赢”。请注意,如果您使用 PDO,请确保设置 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 以使 PDO 将语句发送到 MySQL 进行准备,而不是 PDO 在内部模拟准备工作(没有 MySQL 的参与)。
    • 由于某种原因,您的示例具有可憎之处,它将数据映射器与工厂结合在一个对象中。这很好地说明了大多数人采用的同样被误导的方法——期望同一个映射器同时处理单个实例和集合。但它们并不相同。大多数用于处理集合的底层查询与旨在存储/检索单个对象的查询根本不同。
    • @tereško:当然,它们只是快速输入的示例。您可以用您更喜欢的任何名称替换$userMapper :-) 话虽如此,我相信在一个简单的 ORM 中,让数据映射器也构造它映射的类的集合并不是什么大罪。跨度>
    猜你喜欢
    • 2012-11-23
    • 2010-09-17
    • 2011-06-14
    • 1970-01-01
    • 2010-12-30
    • 2011-10-22
    • 2011-11-05
    • 2013-10-24
    • 1970-01-01
    相关资源
    最近更新 更多