当OptimisticLockException发生时如何避免关闭EntityManager?

时间:2015-08-28 04:26:53

标签: doctrine-orm transactions optimistic-locking

我的问题 - 进程尝试更改已更改且具有最新版本ID的实体。当我在UnitOfWork的代码中执行flush()时,上升的OptimisticLockException并通过catch-all块捕获相同的位置。并在此捕获原则关闭EntityManager。 如果我想跳过这个实体并继续使用ArrayCollection中的另一个,我不应该使用flush()?

尝试重新创建EntityManager:

}catch (OptimisticLockException $e){
    $this->em = $this->container->get('doctrine')->getManager();
    echo "\n||OptimisticLockException.";
    continue;
}

仍然得到

[Doctrine\ORM\ORMException]   
The EntityManager is closed.

奇怪。

如果我这样做

$this->em->lock($entity, LockMode::OPTIMISTIC, $entity->getVersion());

然后执行flush()我得到OptimisticLockException并关闭实体管理器。 如果我做了

$this->getContainer()->get('doctrine')->resetManager();
$em = $doctrine->getManager();

旧数据未注册此实体管理器,我甚至无法在数据库中写入日志,我收到错误:

[Symfony\Component\Debug\Exception\ContextErrorException]  
Notice: Undefined index: 00000000514cef3c000000002ff4781e

2 个答案:

答案 0 :(得分:1)

您应该在尝试刷新实体版本之前检查实体版本以避免异常。换句话说,如果锁失败,则不应调用flush()方法。

您可以使用EntityManager#lock()方法检查是否可以刷新实体。

    /** @var EntityManager $em */
    $entity = $em->getRepository('Post')->find($_REQUEST['id']);

    // Get expected version (easiest way is to have the version number as a hidden form field)
    $expectedVersion = $_REQUEST['version'];

    // Update your entity
    $entity->setText($_REQUEST['text']);

    try {
        //assert you edit right version
        $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);

        //if $em->lock() fails flush() is not called and EntityManager is not closed 
        $em->flush();
    } catch (OptimisticLockException $e) {
        echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
    }

检查Doctrine docs optimistic locking

中的示例

答案 1 :(得分:0)

不幸的是,将近4年后,Doctrine仍然无法从乐观锁中正常恢复。

如果数据库是由另一个服务器或php工作线程更改的,则无法按照文档中的建议使用lock函数。 lock函数仅确保由于实体已加载到内存中,因此当前的php脚本未更改版本号。它不会读取数据库以确保版本号仍为预期版本。

即使它确实读取了数据库,在lock函数检查数据库中的当前版本与执行刷新之间的时间之间仍然存在竞争状态的可能性。

考虑这种情况:

  • 服务器A读取实体,
  • 服务器B读取相同的实体,
  • 服务器B更新数据库,
  • 服务器A更新数据库<==乐观锁异常

调用flush时会触发异常,无法采取任何措施来阻止它。

悲观锁也无济于事,除非您有能力降低性能并实际上(相对)长时间锁定数据库。

Doctrine的解答(update... where version = :expected_version)在理论上是好的。但是,可悲的是,教义被设计为一旦触发异常就变得不可用。 任何例外。每个实体都是独立的。即使可以通过重新读取实体并再次应用更改轻松解决乐观锁,但Doctrine还是很难做到这一点。