教义:如何以编程方式启用急切加载?

Posted

技术标签:

【中文标题】教义:如何以编程方式启用急切加载?【英文标题】:Doctrine: how to enable eager loading programmatically? 【发布时间】:2016-07-04 18:48:57 【问题描述】:

想象以下情况。你有书:

Book(bookId, authorId, title)

和作者:

Author(authorId, name)

每本书都有(为了简单起见)一个作者。

默认情况下,所有关联都配置为 lazy 模式。因此,如果我有这样的场景,当我第一次加载所有书籍、遍历集合并获取每本书的作者时,我将对数据库执行大量查询。

$books = $this->getDoctrine()
        ->getRepository('AppBundle:Book')
        ->findAll();

foreach($books as $b) 
    echo $b->getAuthor()->getName();

我可以以编程方式要求 Doctrine 为这个特定查询急切地加载作者吗(不是通过配置全局)?

相关: In Doctrine 2 can the Fetch Mode (Eager/Lazy etc.) be changed at runtime?

相关: http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/getting-started.html#list-of-bugs

【问题讨论】:

最简单的方法是加入作者关系,最好通过在 Book 存储库中创建自己的“findAllWithAuthors”方法或类似方法。我在这里写了一个类似问题的答案***.com/questions/36250248/… @JimL Omg...在 Doctrine 中真的很难做到吗?! 我不会真的称之为难。在存储库中进行查询只是被认为是最佳实践。您也可以轻松地在控制器中创建查询。标准的简单方法(find、findby 等)通常只是为了帮助您入门。很少有人会在任何真正的应用程序/领域逻辑中坚持下去。 $books = $this->getDoctrine()->createQueryBuilder()->select(['b', 'a'])->from('AppBundle:Book', 'b')->join('b.authors', a')->getQuery()->getResult(); 或在 BookRepository 方法中 return $this->_em->createQueryBuilder('b')->addSelect('a')->join('b.authors', 'a')->getQuery->getResult(); 【参考方案1】:

您可以简单地将书籍和作者之间的mark the association 设置为EAGER(相对于LAZY 的隐含默认值),Doctrine 将始终预先加载该特定关联。

这可以通过添加:

fetch=EAGER

到映射关联。

在运行时执行此操作的一种可能方法是创建Mapped Superclass。超类将定义您的关系和关联的其他部分(而不是您尝试调整的关系)。

然后,要在运行时实际使用该类,您可以创建另外两个具体实现:LazyBookEagerBook。根据您在运行时的场景,您将使用这些具体实现实体中的一个或另一个来构建您的关联。

当然,LazyBook 会将您的 Book -> Author 关联定义为 LAZY 一个(显式或隐式),EagerBook 会将其定义为 EAGER

这不是您定义的真正动态的,但它允许您以编程方式确定在任何给定时间使用哪个关联,同时还可以自行记录它可能是。 p>

【讨论】:

我特别询问程序化方式,以便更灵活地处理查询。但是感谢您提供此选项! @DenisKulagin 嗯,好的。我想我并没有真正看到“在软件中编写”和“编程”之间的区别。他们没有实现相同的目标吗?或者目标是拥有一个要么 LAZY 或EAGER 的关联?如果那是目标,我也不理解用例。 是的,不同之处在于您无法在运行时切换。实际上,这不是关于用例,而是更多关于灵活性。我喜欢调整每个查询,以便它加载所需的确切数据量。不多,但也不少。 @DenisKulagin 我添加了一些关于映射超类的信息,这些信息可能会触及所有重要点(主要是在运行时决定)。【参考方案2】:

这里要理解的一件非常重要的事情是,Doctrine 使用 Data Mapper 模式,而不是 Active Record 模式(例如,您可以在 Yii 框架中找到它):

Doctrine 2 是 php 5.4+ 的对象关系映射器 (ORM) 为 PHP 对象提供透明的持久性。 它使用数据 映射器模式在心脏,旨在完全分离您的 来自关系数据库中的持久性的域/业务逻辑 管理系统。

Doctrine 对程序员的好处是能够专注于 面向对象的业务逻辑和担心持久性只是 次要问题。这并不意味着持久性被低估了 教义 2,但是我们相信有相当多的 如果持久性和实体,面向对象编程的好处 保持分开。

http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/getting-started.html#what-is-doctrine

这实质上意味着,实体类 对它们如何持久保存到数据库一无所知。尽管它们可以有注释类型的注释,但它们只是 ORM 处理的元数据的一种形式。

反过来,这意味着您可以执行与 ActiveRecord 相同的操作,但现在可以在另一个地方完成。我们来看看有什么区别:

在基于 ActiveRecord 的 ORM(如 Yii)中:

$books = Book::model()->with('author')->findAll();

在基于 DataMapper 的 ORM(如 Symfony/Doctrine)中:

$books = $this->getDoctrine()->createQueryBuilder()
  ->select(['b', 'a'])
  ->from('AppBundle:Book', 'b')
  ->join('b.author', a')
  ->addSelect('a')
  ->getQuery()
  ->getResult();

后面的小评论。您在那里构建的查询不是SQL 查询,而是DQL 查询(Doctrine 采用的对象查询语言)。

所以这里的 join/addSelect 很像前一个查询中的 with 只是告诉 ORM 引擎你想加载 author同一个查询。特定的关系元数据(例如,两个基础表的列名)仍然在实体元数据级别定义。

语法(select、from、join)类似于 SQL 故意,但您不应该被它弄糊涂。在这里,构建查询时,您操作的是 ORM 实体,而不是数据库列/表。

【讨论】:

以上是关于教义:如何以编程方式启用急切加载?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 LINQ 中急切加载整个 SQL 表?

如何使用急切加载来查询尽可能少的数据?

教义 orm:绕过延迟加载并在 getter 中预取相关记录

实施急切加载以停止 N+1 - Rails

优化 Rails 急切加载查询以查找所有

Java 中的急切加载是啥?