在Doctrine2中批量加载实体及其所有子实体的最有效方法

时间:2014-11-24 22:04:30

标签: symfony doctrine-orm doctrine

我有一个实体类型I' ll调用父与一个实体的一对多关系我将调用child(许多孩子属于单个父母)。

我想做的是尽可能高效地加载父实体及其所有子代,并尽可能少地查询。

默认情况下,使用doctrine,如果我执行类似于父实体的100的操作,然后遍历它们并对Children执行操作,则至少会有101个查询,一个用于加载Parent,另一个用于获取所有每个父母的孩子。如果我尝试编写一个在一次传递中加载所有对象的查询,它会变得非常慢,加载我只能假设的是一个笛卡尔对象,其中包含每个子项的所有父级和子级属性的整行。如果我有多个子实体,这个问题会变得更加严重。

我能想到的唯一解决方案是独立查询所有父母和所有孩子,然后在嵌套循环中关联它们。这将其减少到只有两个查询,但似乎......不对。有人对我有任何见解吗?这基本上就是我的想法:

//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;
        }
    }
}

编辑1:

回应Tom Corrigan的评论:

使用查询构建器就像您的建议一样:

$qb->select(['p', 'c'])
   ->from('Parent', 'p')
   ->leftJoin('p.children', 'c')
;

我确实加载了整个对象,但性能打击很荒谬。使用上面的查询加载两个父实体,总共大约12个子实体花了将近40秒。如果我加载父实体那么加载两个子实体需要大约5ms。

编辑2:

确定只有在我根据连接的多对多表查询时才会出现非常长的加载时间问题。如果我像上面描述的那样做一个简单的选择,那么它的工作速度非常快,所以谢谢Tom,你完全回答了我的问题。

我不明白 - 这完全是一个不同的问题,但我在这里提到它是因为它在某种程度上是相关的 - 这就是为什么,当我做多个选择时,它如果我按其中一个选定的子列进行过滤,则会花费很长的时间。

此示例在混合中添加了两个实体,组(人多对多)和应用程序(一对多与组)。

$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)
;

此查询将花费将近一分钟的时间来处理,而如果我做同样的事情而没有额外的选择,那么:

$qb->select('p')
   ->from('Parent', 'p')
   ->leftJoin('p.children', 'c')
   ->join('p.groups','g')
   ->join('g.application','a')
   ->where('a.id = :applicationId')       
   ->setMaxResults(1)
;

需要几个MS,并且还需要几毫秒才能添加其他实体。

有什么想法吗?

2 个答案:

答案 0 :(得分:4)

这在Doctrine中很容易(正如您所期望的那样),但是我对文档的搜索没有提供如何执行此操作的明确说明。然而,Guilherme Blanco有fantastic talk解释了如何做到这一点。 (从幻灯片22开始看)

我注意到您提供的示例使用了DQL,但也可以使用查询构建器执行此操作。

在DQL中:

SELECT p, c
FROM Parent p
LEFT JOIN p.children c

使用QueryBuilder

$qb->select(['p', 'c'])
   ->from('Parent', 'p')
   ->leftJoin('p.children', 'c')
;

运行上述任一查询只会触发一次数据库,并返回一个父对象数组,其所有子对象都完全充满水分。如果您需要加入更多实体,您可以这样做,但只记得您是否希望doctrine为您实际修改这些对象,那么您必须将连接的实体添加到select语句中。例如:

$qb->select(['p', 'c', 'gc'])
   ->from('Parent', 'p')
   ->leftJoin('p.children', 'c')
   ->leftJoin('c.grandchildren', 'gc')
;

答案 1 :(得分:0)