EntityManager 已关闭

Posted

技术标签:

【中文标题】EntityManager 已关闭【英文标题】:The EntityManager is closed 【发布时间】:2012-12-24 21:07:02 【问题描述】:
[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();

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

【问题讨论】:

实体管理器为什么会关闭? @JaySheth 实体管理器可以在 DBAL 异常之后关闭,或者如果您在刷新之前执行 EntityManager->clear()。我见过一些人使用 DBAL 异常来分支执行流程,然后以 EntityManager 关闭错误告终。如果您收到此错误,则说明您的程序中的执行流程有问题。 @AlanChavez - 我收到此错误是因为我正在使用 Doctrine 将信号量标志写入多个线程同时访问的表中。 mysql 将尝试创建信号量的两个竞争线程之一出错,因为键约束意味着它们中只有一个可以成功。 IMO 在 Doctrine 中有一个缺陷,它不允许您安全地处理 expected MySQL 错误。为什么要因为一条 INSERT 语句发生冲突而断开整个 MySQL 连接? 如果您尝试将异常记录到 app.exception_listener 中的数据库但异常(例如违反约束)关闭了连接,也会看到此错误。 【参考方案1】:

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

我发现正确的解决方案是将persistflush 方法包装在try 中,但使用explicit transactions。

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

这是一个例子:

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

* managerRegistry 指的是Doctrine\Persistence\ManagerRegistry

来源:This message 在 Doctrine' Slack 中的#orm 频道。

【讨论】:

【参考方案2】:

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

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

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

$object->setIsActive(false);

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

改成后:

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

问题消失了。

【讨论】:

【参考方案3】:
The EntityManager is closed.

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

【讨论】:

【参考方案4】:

这就是你在 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”作为服务参数!

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

【讨论】:

新版本没有这种方法。【参考方案5】:

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

【讨论】:

【参考方案6】:

Symfony v4.1.6

学说 v2.9.0

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

    在您的存储库中访问注册表


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

    将有风险的代码包装到事务中并在出现异常时重置管理器


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

【讨论】:

【参考方案7】:

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

@ORM\Column(name="`order`", type="integer", nullable=false)

【讨论】:

【参考方案8】:

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

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

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

一个组(例如 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();

希望对你有帮助:)

【讨论】:

很棒的解释。我发现了一些类似的东西,并认为为您的答案做出贡献会很好。非常感谢。【参考方案9】:

我在测试 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.

所以检查日志是个好主意

【讨论】:

您能否提供更多信息,说明第一个与第二个之间的关系?【参考方案10】:

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

【讨论】:

【参考方案11】:

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

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

Doctrine 2 Exception EntityManager is closed

【讨论】:

【参考方案12】:

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

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

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

【讨论】:

【参考方案13】:

Symfony 2.0

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

Symfony 2.1+

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

【讨论】:

警告: resetEntityManager 自 Symfony 2.1 起已被弃用。请改用resetManager 这是否也会重置工作单元? @flu 考虑到 EntityManager 类管理 UnitOfWork 类,我怀疑它会。但是,我没有对此进行测试,因此无法确定。【参考方案14】:

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

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

【讨论】:

使用任何参数调用 Doctrine\ORM\EntityManager::clear() 以清除特定实体已被弃用,并且在 Doctrine ORM 3.0 中不受支持【参考方案15】:

我遇到了同样的问题。看了好几个地方,这里是我的处理方式。

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

希望这对某人有所帮助!

【讨论】:

【参考方案16】:

在控制器中。

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

/** 
* @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++;
                   
                        

【讨论】:

【参考方案17】:

我遇到了这个问题。我就是这样解决的。

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

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

【讨论】:

【参考方案18】:
// 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();

【讨论】:

【参考方案19】:

我的解决方案。

在做任何事情之前检查:

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

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

【讨论】:

当 di 容器本身不可用时,这会更好。谢谢。 您可能还想在第三个参数中传递 $this->entityManager->getEventManager()。 注意:应该使用$this->entityManager::create,与方法声明相同:public static function create(...)【参考方案20】:

你可以重置你的 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();

【讨论】:

【参考方案21】:

这是一个非常棘手的问题,因为至少对于 Symfony 2.0 和 Doctrine 2.1 来说,在 EntityManager 关闭后无法以任何方式重新打开它。

我发现解决这个问题的唯一方法是创建自己的 DBAL Connection 类,包装 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 方法。使用闭包可以减轻一些痛苦。

【讨论】:

以上是关于EntityManager 已关闭的主要内容,如果未能解决你的问题,请参考以下文章

EntityManager 已关闭?

播放框架:EntityManager 已关闭

我们是不是需要在选择调用时关闭 EntityManager 资源

我必须关闭()每个 EntityManager 吗?

使用 HikariCP 时在每次 db 操作后关闭 EntityManager

JPA EntityManager 查找方法不起作用[关闭]