在foreach循环内部或外部调用flush()之间的区别,使用哪一个?

时间:2015-01-16 16:46:43

标签: php symfony doctrine-orm

我有一段时间怀疑,但现在是时候问一下了。请参阅下面的代码并在$someVar中包含大量项目,例如200项:

// First approach
foreach($someVar as $item) {
    $item = $em->getRepository('someEntity')->find($item['value']);
    $em->remove($item);
    $em->flush();
}

// Second approach
foreach($someVar as $item) {
    $item = $em->getRepository('someEntity')->find($item['value']);
    $em->remove($item);
}

$em->flush();
  • 两个电话都会这样做吗?意味着从DB中删除记录?
  • 在性能水平上,哪一个最好用? (学说有时表现为记忆杀手)
  • 如果两种方法都很好,我也可以使用相同的每个UPDATE查询吗?
  • 如果由于某种原因导致任何查询失败,我如何抓住哪一个?可能是Doctrine
  • 给出的错误

使用真实案例进行测试

在答案中提供了很好的信息,我仍然怀疑。看看这段代码:

foreach ($request->request->get( 'items' ) as $item) {
    $items = $em->getRepository( 'AppBundle:FacturaProductoSolicitud' )->find( $item['value'] );

    try {
        $em->remove( $items );
        $em->flush();

        array_push($itemsRemoved, $item['value']);
        $response['itemsRemoved'] = $itemsRemoved;
        $response['success'] = true;
    } catch ( \Exception $e ) {
        dump( $e->getMessage() );

        array_push($itemsNonRemoved, $item['value']);
        $response['itemsNonRemoved'] = $itemsNonRemoved;
        $response['success'] = false;
    }
}

我在这里使用try {} catch() {}句子,因为我需要知道删除了哪个$item['value']才能将其添加到正确的数组中(请参阅$itemsRemoved和{{1还有$itemsNonRemoved为每个循环执行,我知道不好的做法,但是,从foreach循环中取出flush()并在try-catch中执行,是否有任何方法可以获得flush被删除了吗?怎么样?

1 个答案:

答案 0 :(得分:7)

实际上,每次删除后运行flush()都是反模式。理想情况下,您应该在所有查询结束时运行一次。

在大多数情况下,Doctrine 2已经为您处理了正确的事务划分:所有写操作(INSERT / UPDATE / DELETE)都排队,直到调用EntityManager #flush(),将所有这些更改包装在一个事务。

但是,如果您想要更高的一致性,可以将查询包装在事务中。事实上,正如你可以在其best practices中阅读的那样,Doctrine鼓励它。

这两个电话都会这样做吗?意味着从数据库中删除记录?

是的,虽然在实体上调用remove不会导致在数据库上立即发出SQL DELETE。在下一次涉及该实体的EntityManager#flush()调用时,将删除该实体。这意味着仍可以查询计划删除的实体,并将其显示在查询和集合结果中。

因此,在循环内部刷新意味着大量SQL查询和对数据库的访问,并且实体将立即被删除。

在循环外部刷新意味着Doctrine执行了一个有效的事务(对数据库的一次访问),但实际上删除了实体,直到调用了flush。实体只会被标记为已删除。

在性能水平,哪一个最好用? (学说有时表现为记忆杀手)

毫无疑问,在循环外冲洗。这也是最好的做法。在某些情况下,每次持久化/删除/更新实体时,您确实需要执行刷新,但很少。

如果两种方法都很好,我是否也可以使用相同的每个UPDATE查询?

同样适用于更新/持久性。尽量避免冲洗内部循环。

总结一下,看看documentation。这真的很好解释。

如果由于某种原因导致任何查询失败,我如何抓住哪一个?可能是Doctrine

给出的错误

您始终可以将flush包装在try / catch块中,并优雅地捕获查询失败时抛出的异常。

try {
   $em->flush()
}(\Exception $e) {
    // do stuff
    throw $e;// re-thrown Exception
}

当使用隐式事务划分并且在EntityManager #flush()期间发生异常时,事务将自动回滚并且EntityManager关闭。

有关here主题的更多信息。

<强>更新

在您提供的代码中,如果您在循环外使用flush,则所有删除操作都属于同一事务。这意味着如果它们中的任何一个失败,则抛出异常并发出回滚(执行的所有已删除操作都将被回滚,因此不会保留在DB上)。

例如:假设我们有四个带有id 1,2,3,4,5,6的项目,并假设删除4个失败。

第一个选项 - &gt;刷新内部循环:1,2,3被删除。 4失败抛出异常并结束。

第二个选项 - &gt;刷新外部循环:4次失败,回滚,没有删除,程序结束。

如果您要实现的行为是案例1中显示的行为,则可以选择一个您正在使用的行为。但是,它在性能方面确实很昂贵。

然而,有更好的解决方案:例如,您可以使用preRemove / postRemove事件的组合来存储在刷新中成功删除的那些实体的ID(或任何您想要的值)(尽管不是持久的,因为回滚)。例如,您可以将它们存储在属于该类的静态数组中(或使用单例或其他)。然后在异常的catch子句中,您可以使用该数组迭代并对这些项执行删除操作(当然,在循环外部刷新)。

然后,您可以返回数组,以便让用户知道您实际删除了这些实体,并且因为删除过程中存在问题而导致错误。