97
[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();

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

4

21 回答 21

80

我的解决方案。

在做任何事情之前检查:

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

所有实体都将被保存。但它对于特定类或某些情况很方便。如果你有一些注入了 entitymanager 的服务,它仍然会被关闭。

于 2013-09-29T10:36:58.403 回答
38

Symfony 2.0

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

Symfony 2.1+

$em = $this->getDoctrine()->resetManager();
于 2014-02-13T10:29:39.813 回答
33

这就是我解决“EntityManager 已关闭”的教义的方法。问题。基本上每次出现异常(即重复键)或不为必填列提供数据时,都会导致 Doctrine 关闭实体管理器。如果您仍想与数据库交互,则必须通过调用JGrinonresetManager()提到的方法来重置实体管理器。

在我的应用程序中,我运行了多个 RabbitMQ 消费者,它们都在做同样的事情:检查数据库中是否存在实体,如果是则返回它,如果没有则创建它然后返回它。在检查该实体是否已经存在和创建它之间的几毫秒内,另一个消费者碰巧做了同样的事情并创建了丢失的实体,使另一个消费者产生了重复键异常(竞争条件)。

这导致了软件设计问题。基本上我想做的是在一个事务中创建所有实体。这对大多数人来说可能感觉很自然,但在我的情况下,这在概念上肯定是错误的。考虑以下问题:我必须存储一个具有这些依赖关系的足球比赛实体。

  • 一个组(例如 A 组、B 组...)
  • 一轮(例如半决赛......)
  • 场地(即比赛进行的体育场)
  • 比赛状态(例如半场、全场)
  • 参加比赛的两支球队
  • 比赛本身

现在,为什么场地创建应该与比赛在同一个交易中?可能是我刚刚收到了一个不在我的数据库中的新场地,所以我必须先创建它。但也可能是该场地可能举办另一场比赛,因此另一位消费者可能会同时尝试创建它。所以我必须做的是首先在单独的事务中创建所有依赖项,确保我在重复键异常中重置实体管理器。我想说匹配项旁边的所有实体都可以定义为“共享”,因为它们可能是其他消费者的其他交易的一部分。那里没有“共享”的东西是匹配本身不太可能由两个消费者同时创建。

所有这些也导致了另一个问题。如果您重置实体管理器,那么您在重置之前检索到的所有对象对于 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();

我希望它有帮助:)

于 2015-06-29T10:00:48.477 回答
27

This is a very tricky problem since, at least for Symfony 2.0 and Doctrine 2.1, it is not possible in any way to reopen the EntityManager after it closes.

The only way I found to overcome this problem is to create your own DBAL Connection class, wrap the Doctrine one and provide exception handling (e.g. retrying several times before popping the exception out to the EntityManager). It is a bit hacky and I'm afraid it can cause some inconsistency in transactional environments (i.e. I'm not really sure of what happens if the failing query is in the middle of a transaction).

An example configuration to go for this way is:

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

The class should start more or less like this:

namespace Your\DBAL;

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

A very annoying thing is that you have to override each method of Connection providing your exception-handling wrapper. Using closures can ease some pain there.

于 2013-01-10T15:23:20.667 回答
18

你可以重置你的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();
于 2013-05-16T19:06:48.360 回答
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:

服务.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();
    }
}
于 2019-06-17T12:36:05.043 回答
6

Symfony v4.1.6

教义 v2.9.0

处理在存储库中插入重复项

  1. 访问您的仓库中的注册表


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

于 2018-12-06T08:51:24.090 回答
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++;
    }               
  }                      
}
于 2015-07-16T11:37:02.890 回答
3

我发现了一篇关于这个问题的有趣文章

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

Doctrine 2 异常 EntityManager 已关闭

于 2019-04-18T15:18:05.800 回答
3

值得一提的是,我发现这个问题发生在批量导入命令中,因为 try/catch 循环捕获了一个em->flush()我没有做任何事情的 SQL 错误(带有 )。在我的情况下,这是因为我试图插入一条不可为空的属性保留为空的记录。

通常这会导致发生严重异常并停止命令或控制器,但我只是记录了这个问题并继续。SQL 错误导致实体管理器关闭。

检查您的dev.log文件中是否存在类似这样的愚蠢 SQL 错误,因为这可能是您的错。:)

于 2018-09-04T10:23:58.303 回答
1

同样的问题,通过简单的代码重构解决了。当必填字段为空时,有时会出现问题,在执行任何操作之前,请尝试重构您的代码。更好的工作流程可以解决问题。

于 2020-10-16T06:51:21.890 回答
1

在 Symfony 5 / Doctrine ORM 2.10 中,该resetEntityManager方法不可用。

我发现一个正确的解决方案是将persistflush方法包装在 a 中try,但使用显式事务

当发生异常时,我使用resetManager方法。

这是一个例子:

try {
    $this->entityManager->beginTransaction();
    $this->entityManager->persist($entity);
    $this->entityManager->flush();
    $this->entityManager->commit();
} catch (Exception $e) {
    $this->entityManager->rollback();
    $this->managerRegistry->resetManager();
}

*managerRegistryDoctrine\Persistence\ManagerRegistry

来源:这条消息在 Doctrine' Slack 的 #orm 频道。

于 2021-12-08T05:15:56.037 回答
1

当我尝试在不为该属性设置默认值的情况下使用不可为空的列保留对象时,我遇到了同样的问题。

/**
 * @ORM\Column(type="boolean")
 */
protected $isActive;

在某些情况下,在控制器中准备该对象时,我不会通过以下方式明确设置该属性:

$object->setIsActive(false);

所以最后,即使数据库中的那一列的默认值为 0,Doctrine 也试图在该列中保存 NULL。

改为:

/**
 * @ORM\Column(type="boolean")
 */
protected $isActive = false;

问题消失了。

于 2021-09-29T22:03:19.183 回答
0

我有这个问题。这就是我修复它的方法。

尝试刷新或持续时,连接似乎关闭。试图重新打开它是一个糟糕的选择,因为会产生新的问题。我试图理解为什么连接被关闭,发现我在持久化之前做了太多的修改。

persist() 早先解决了这个问题。

于 2015-02-13T12:58:17.830 回答
0

这就是你在 Symfony3 中重置实体管理器的方式。如果它已关闭,它应该重新打开 em:

在控制器中:

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

在服务中:

  if (!$this->em->isOpen()) {
        $this->managerRegistry->resetManager('managername');
        $this->em = $this->managerRegistry->getManager('default');
    }

    $this->em->persist(...);

不要忘记在 service.yml 中注入“@doctrine”作为服务参数!

我想知道,如果不同的方法同时尝试同时访问同一个实体,是否会发生此问题?

于 2021-02-26T15:10:26.700 回答
0

我在测试 Symfony 4.3.2 中的更改时遇到了同样的问题

我将日志级别降低到 INFO

并再次运行测试

记录显示:

console.ERROR: Error thrown while running command "doctrine:schema:create". Message: "[Semantical Error] The annotation "@ORM\Id" in property App\Entity\Common::$id was never imported. Did you maybe forget to add a "use" statement for this annotation?" {"exception":"[object] (Doctrine\\Common\\Annotations\\AnnotationException(code: 0): [Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation? at C:\\xampp\\htdocs\\dirty7s\\vendor\\doctrine\\annotations\\lib\\Doctrine\\Common\\Annotations\\AnnotationException.php:54)","command":"doctrine:schema:create","message":"[Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation?"} []

这意味着代码中的某些错误会导致:

Doctrine\ORM\ORMException: The EntityManager is closed.

所以检查日志是个好主意

于 2019-07-21T09:19:48.523 回答
-1

这确实是个老问题,但我也遇到了类似的问题。我正在做这样的事情:

// entity
$entityOne = $this->em->find(Parent::class, 1);

// do something on other entites (SomeEntityClass)
$this->em->persist($entity);
$this->em->flush();
$this->em->clear();

// and at end I was trying to save changes to first one by
$this->em->persist($entityOne);
$this->em->flush();
$this->em->clear();

问题是清除所有实体,包括第一个实体并抛出错误EntityManager 已关闭。

在我的情况下,解决方案是对不同类型的实体进行明确,并留$entityOne在 EM 下:

$this->em->clear(SomeEntityClass::class);
于 2016-03-02T15:48:19.103 回答
-1
The EntityManager is closed.

我有同样的问题。原因是数据库表中缺少列 - 我只需要运行迁移。

于 2021-04-06T18:15:47.993 回答
-2
// first need to reset current manager
$em->resetManager();
// and then get new
$em = $this->getContainer()->get("doctrine");
// or in this way, depending of your environment:
$em = $this->getDoctrine();
于 2014-09-08T22:00:29.690 回答
-2

我遇到了同样的问题。在看了几个地方之后,我是如何处理的。

//function in some model/utility
function someFunction($em){
    try{
        //code which may throw exception and lead to closing of entity manager
    }
    catch(Exception $e){
        //handle exception
        return false;
    }
    return true;
}

//in controller assuming entity manager is in $this->em 
$result = someFunction($this->em);
if(!$result){
    $this->getDoctrine()->resetEntityManager();
    $this->em = $this->getDoctrine()->getManager();
}

希望这对某人有帮助!

于 2015-11-19T13:23:38.670 回答
-3

我在使用 Symfony 5 / Doctrine 2 时遇到了同样的错误。我的一个字段使用 MySQL 保留字“order”命名,导致 DBALException。当您想使用保留字时,您必须使用反引号转义它的名称。以注释形式:

@ORM\Column(name="`order`", type="integer", nullable=false)
于 2020-02-10T15:42:39.143 回答