实体管理器已关闭。

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

在插入数据时遇到DBAL异常后,EntityManager关闭了,我无法重新连接它。

我尝试过像这样的操作,但没有连接成功。

$this->em->close();
$this->set('doctrine.orm.entity_manager', null);
$this->set('doctrine.orm.default_entity_manager', null);
$this->get('doctrine')->resetEntityManager();
$this->em = $this->get('doctrine')->getEntityManager();

有人知道如何重新连接吗?


1
实体管理器为什么会关闭? - Jay Sheth
3
实体管理器可能会在出现DBAL异常或在flush之前进行EntityManager->clear()后关闭。我看到一些人使用DBAL异常来分支执行流程,结果导致实体管理器关闭错误。如果你遇到这个错误,说明程序的执行流程有问题。 - ILikeTacos
7
我之所以出现这个错误,是因为我正在使用Doctrine将一个信号标志写入一个被多个线程同时访问的表中。MySQL会在两个竞争的线程之一尝试创建信号量时报错,因为关键约束意味着只有其中一个线程能成功。我的看法是,Doctrine存在缺陷,无法安全地处理预期的MySQL错误。为什么因为一个INSERT语句存在冲突就要断开整个MySQL连接呢? - StampyCode
3
如果您尝试将异常记录到app.exception_listener中的数据库,但异常(例如约束违规)关闭了连接,则还会看到此错误。 - Lg102
21个回答

91

我的解决方案。

在做任何事情之前,请检查:

if (!$this->entityManager->isOpen()) {
    $this->entityManager = $this->entityManager->create(
        $this->entityManager->getConnection(),
        $this->entityManager->getConfiguration()
    );
}

所有实体都将被保存,但对于特定的类或某些情况很有用。如果您有一些已注入entitymanager的服务,则它仍将关闭。


当 DI 容器本身不可用时,这种方法要好得多。谢谢。 - Hari K T
2
你可能还想在第三个参数中传递 $this->entityManager->getEventManager()。 - Medhat Gayed
1
注意:应该使用$this->entityManager::create,与方法声明相同:public static function create(...) - long

42
这是我解决Doctrine "EntityManager已关闭"问题的方法。
基本上每次出现异常(即重复键)或者未提供必填列的数据,Doctrine就会关闭EntityManager。如果你仍想与数据库交互,你需要调用resetManager()方法来重置Entity Manger,正如JGrinon所述。
在我的应用程序中,我运行了多个RabbitMQ消费者,它们都在做同样的事情:检查数据库中是否存在某个实体,如果存在则返回该实体,否则创建它然后返回它。
在检查实体是否存在并进行创建之间的几毫秒内,另一个消费者也在做同样的事情,并创建了缺失的实体,导致另一个消费者遭遇了重复键异常(竞态条件)。
这导致了软件设计问题。基本上,我试图在一个事务中创建所有实体。对大多数人来说,这可能感觉很自然,但在我的情况下确实在概念上是错误的。考虑以下问题:我必须存储一个足球比赛实体,其中包含以下依赖项:
• 一个分组(例如A组,B组...)
• 一个轮次(例如半决赛...)
• 一个场馆(即比赛举行的体育场)
• 一个比赛状态(例如中场,全场)
• 两个参加比赛的队伍
• 比赛本身
现在,为什么场馆创建应该与比赛在同一事务中进行?可能我刚刚收到了一个新场馆,但它并不在我的数据库中,所以我必须先创建它。但它也可能会举办其他比赛,因此另一个消费者可能会尝试同时创建它。所以我必须首先在单独的事务中创建所有依赖项,确保在重复键异常时重置实体管理器。我认为除了比赛之外,在其中的所有实体都可以被定义为“共享”,因为它们可能成为其他消费者中的其他事务的一部分。在其中没有“共享”的是比赛本身,它不太可能被两个消费者同时创建。因此,在最后一个事务中,我希望只看到比赛和两个队伍之间的关系。
所有这些也导致了另一个问题。如果你重置了Entity Manager,你之前检索到的所有对象对于Doctrine来说都是全新的。所以Doctrine不会尝试在它们上运行UPDATE,而是INSERT!因此,请确保在逻辑上正确的事务中创建所有依赖关系,然后从数据库中检索所有对象,再将它们设置为目标实体。请考虑以下代码作为示例:
$group = $this->createGroupIfDoesNotExist($groupData);

$match->setGroup($group); // this is NOT OK!

$venue = $this->createVenueIfDoesNotExist($venueData);

$round = $this->createRoundIfDoesNotExist($roundData);

/**
 * If the venue creation generates a duplicate key exception
 * we are forced to reset the entity manager in order to proceed
 * with the round creation and so we'll loose the group reference.
 * Meaning that Doctrine will try to persist the group as new even
 * if it's already there in the database.
 */

这就是我认为应该这样做的方式。

$group = $this->createGroupIfDoesNotExist($groupData); // first transaction, reset if duplicated
$venue = $this->createVenueIfDoesNotExist($venueData); // second transaction, reset if duplicated
$round = $this->createRoundIfDoesNotExist($roundData); // third transaction, reset if duplicated

// we fetch all the entities back directly from the database
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);

// we finally set them now that no exceptions are going to happen
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);

// match and teams relation...
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);

$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);

$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);

// last transaction!
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();

希望这能有所帮助 :)


非常棒的解释。我找到了一些类似的内容,想为你的回答做出贡献。非常感谢。 - Anjana Silva
1
这里的关键是“基本上每次出现异常”。我犯了一个错误,在flush()上捕获并丢弃了一个异常,因此EM被关闭了,而我并不知道。现在我会以不同的方式处理flush()上的异常;非常感谢。 - Benoit Duffez

40

Symfony 2.0:

$em = $this->getDoctrine()->resetEntityManager();

Symfony 2.1+:

$em = $this->getDoctrine()->resetManager();

6
警告:自Symfony 2.1版本开始,resetEntityManager已被弃用。请改用resetManager。 - Francesco Casula
这是否也会重置工作单元? - flu
考虑到EntityManager类管理UnitOfWork类,我认为它应该可以。但是,我还没有测试过,所以不能确定。 - Ryall
1
我不明白为什么所有关于 resetManager() 的使用示例都要用它的返回值再次设置 $em。在 Symfony 5 中,当我尝试之后,你可以在不重新设置的情况下继续使用 $em 中相同的值。 - Nuryagdy Mustapayev

28

这是一个非常棘手的问题,因为至少对于Symfony 2.0和Doctrine 2.1来说,在EntityManager关闭后,无论如何都不可能重新打开它。

我发现克服这个问题的唯一方法是创建自己的DBAL连接类,包装Doctrine连接并提供异常处理(例如,在将异常弹出到EntityManager之前尝试多次重试)。这有点hacky,我担心在事务环境中可能会导致一些不一致性(即,如果失败的查询在事务中间,我不确定会发生什么)。

一个可以参考的示例配置:

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        driver:   %database_driver%
        host:     %database_host%
        user:     %database_user%
        password: %database_password%
        charset:  %database_charset%
        wrapper_class: Your\DBAL\ReopeningConnectionWrapper

这个课程应该大致从以下内容开始:

namespace Your\DBAL;

class ReopeningConnectionWrapper extends Doctrine\DBAL\Connection {
  // ...
}

一件很烦人的事情是你必须覆盖每一个Connection方法并提供你的异常处理程序。使用闭包可以在这方面减轻一些痛苦。


17

您可以重置您的EM,因此

// reset the EM and all aias
$container = $this->container;
$container->set('doctrine.orm.entity_manager', null);
$container->set('doctrine.orm.default_entity_manager', null);
// get a fresh EM
$em = $this->getDoctrine()->getManager();

13
在Symfony 4.2+中,您需要使用此软件包:
composer require symfony/proxy-manager-bridge

否则,您将收到异常:

Resetting a non-lazy manager service is not supported. Declare the "doctrine.orm.default_entity_manager" service as lazy.  

您可以像这样重置entityManager:

services.yaml:

App\Foo:
    - '@doctrine.orm.entity_manager'
    - '@doctrine'

Foo.php:

use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;


 try {
    $this->entityManager->persist($entity);
    $this->entityManager->flush();
} catch (DBALException $e) {
    if (!$this->entityManager->isOpen()) {
        $this->entityManager = $this->doctrine->resetManager();
    }
}

6

我找到了一篇与这个问题相关的有趣文章

if (!$entityManager->isOpen()) {
  $entityManager = $entityManager->create(
    $entityManager->getConnection(), $entityManager->getConfiguration());
}

Doctrine 2异常:实体管理器已关闭


6

Symfony v4.1.6

Doctrine v2.9.0

处理在库中插入重复数据的过程

  1. 在您的repo中获取访问注册表的权限


    //begin of repo
    
    /** @var RegistryInterface */
    protected $registry;
    
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
        parent::__construct($registry, YourEntity::class);
    }

  1. 将有风险的代码放入事务中,并在出现异常时重置管理器


    //in repo method
    $em = $this->getEntityManager();
    
    $em->beginTransaction();
    try {
        $em->persist($yourEntityThatCanBeDuplicate);
        $em->flush();
        $em->commit();
    
    } catch (\Throwable $e) {
        //Rollback all nested transactions
        while ($em->getConnection()->getTransactionNestingLevel() > 0) {
            $em->rollback();
        }
        
        //Reset the default em
        if (!$em->isOpen()) {
            $this->registry->resetManager();
        }
    }


4
就我所知,这个问题发生在批量导入命令中,因为 try/catch 循环捕获了一个 SQL 错误(使用 em->flush()),但我没有采取任何措施。在我的案例中,这是因为我试图插入一个非空属性为空的记录。
通常情况下,这会导致关键异常发生,命令或控制器停止运行,但我只是记录了这个问题并继续运行。SQL 错误导致实体管理器关闭。
查看你的 dev.log 文件,看看是否有类似这样的愚蠢的 SQL 错误,因为这可能是你的错。 :)

4

在控制器中。

异常会关闭实体管理器。这会给批量插入带来麻烦。 为了继续操作,需要重新定义它。

/** 
* @var  \Doctrine\ORM\EntityManager
*/
$em = $this->getDoctrine()->getManager();

foreach($to_insert AS $data)
{
    if(!$em->isOpen())
    {
        $this->getDoctrine()->resetManager();
        $em = $this->getDoctrine()->getManager();
    }

  $entity = new \Entity();
  $entity->setUniqueNumber($data['number']);
  $em->persist($entity);

  try
  {
    $em->flush();
    $counter++;
  }
  catch(\Doctrine\DBAL\DBALException $e)
  {
    if($e->getPrevious()->getCode() != '23000')
    {   
      /**
      * if its not the error code for a duplicate key 
      * value then rethrow the exception
      */
      throw $e;
    }
    else
    {
      $duplication++;
    }               
  }                      
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接