Pez*_*Pez 3 doctrine symfony doctrine-orm
我有一种实体类型,我将其称为父级,与我称为子级的实体之间存在一对多关系(许多子级属于单个父级)。
我想要做的是尽可能高效地加载父实体及其所有子实体,并使用尽可能少的查询。
默认情况下,使用学说,如果我执行诸如加载 100 个父实体之类的操作,然后循环遍历它们并对子实体执行操作,则至少需要 101 个查询,一个用于加载父实体,另一个用于获取每个子实体的所有子实体父母。如果我尝试编写一个在一次通过中加载所有对象的查询,它会变得非常慢,加载我只能假设是一个笛卡尔对象,每个孩子的所有父和子属性都包含一整行。如果我有多个子实体,这个问题会变得更具破坏性,我就是这样做的。
我能想到的唯一解决方案是独立查询所有父级和所有子级,然后将它们关联到嵌套循环中。这将它减少到只有两个查询,但似乎......不对。有人对我有任何见解吗?这基本上是我的想法:
//First, select an array of all parent objects
$parents = "SELECT p FROM parent p WHERE 1 LIMIT 100";
//Select all child elements
$children = "SELECT c FROM children c WHERE c.parent IN ($parents)";
//Loop through all elements and assign them to parents, and parents to children
foreach($children as $child){
foreach($parents as $parent){
//All children will have loaded references to their parent object
if ($child->getParent()->getId() === $parent->getId()){
//This child belongs to this parent
$parent->addChild($child);
$child->setParent($parent);
continue;
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑1:
回应 Tom Corrigan 在下面的评论:
像您的建议一样使用查询构建器:
$qb->select(['p', 'c'])
->from('Parent', 'p')
->leftJoin('p.children', 'c')
;
Run Code Online (Sandbox Code Playgroud)
我确实加载了整个对象,但性能下降是荒谬的。使用上面的查询加载两个父实体,总共有大约 12 个子实体,耗时将近 40 秒。如果我加载父实体然后加载两个子实体大约需要 5 毫秒。
编辑2:
好的,只有当我基于连接的多对多表进行查询时,才会出现令人难以置信的长加载时间问题。如果我像上面描述的那样做一个简单的选择,它的工作速度会很快,所以谢谢汤姆,你完美地回答了我的问题。
我不明白——这完全是一个不同的问题,但我会在这里提到它,因为它在某种程度上是相关的——这是为什么,当我进行多项选择时,如果我需要很长时间按选定的子列之一过滤。
此示例将另外两个实体添加到组合中,即组(多人多对多)和应用程序(多人一对多)。
$qb->select(['p', 'c', 'g', 'a'])
->from('Parent', 'p')
->leftJoin('p.children', 'c')
->join('p.groups','g')
->join('g.application','a')
->where('a.id = :applicationId')
->addGroupBy('p')
->setMaxResults(1)
;
Run Code Online (Sandbox Code Playgroud)
这个查询需要将近一分钟的时间来处理,而如果我在没有额外选择的情况下做同样的事情,像这样:
$qb->select('p')
->from('Parent', 'p')
->leftJoin('p.children', 'c')
->join('p.groups','g')
->join('g.application','a')
->where('a.id = :applicationId')
->setMaxResults(1)
;
Run Code Online (Sandbox Code Playgroud)
它需要几毫秒,并且添加额外的实体需要几毫秒。
有什么想法吗?
这在 Doctrine 中非常简单(如您所料),但我对文档的搜索并没有给出如何做到这一点的明确解释。然而,Guilherme Blanco的精彩演讲解释了如何做到这一点。(从幻灯片 22 开始看)
我注意到您提供的示例使用了 DQL,但也可以使用查询构建器来执行此操作。
在 DQL 中:
SELECT p, c
FROM Parent p
LEFT JOIN p.children c
Run Code Online (Sandbox Code Playgroud)
使用 QueryBuilder
$qb->select(['p', 'c'])
->from('Parent', 'p')
->leftJoin('p.children', 'c')
;
Run Code Online (Sandbox Code Playgroud)
运行上述任何一个查询都只会访问数据库一次,并返回一个 Parent 对象数组,其中所有子对象都完全水合。如果您需要加入更多实体,您可以这样做,但请记住,如果您希望学说实际为您水合这些对象,那么您必须将加入的实体添加到选择语句中。例如:
$qb->select(['p', 'c', 'gc'])
->from('Parent', 'p')
->leftJoin('p.children', 'c')
->leftJoin('c.grandchildren', 'gc')
;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3619 次 |
| 最近记录: |