EntityManager已关闭


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

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


实体经理为何关闭?
杰伊·谢思

2
@JaySheth发生DBAL异常后,或者如果在刷新前正在执行EntityManager-> clear(),则实体管理器可能会关闭。我见过有人使用DBAL异常来分支执行流,然后最终出现EntityManager关闭错误。如果遇到此错误,则程序的执行流程中有问题。
ILikeTacos 2014年

5
@AlanChavez-我收到此错误,因为我正在使用Doctrine将信号量标志写入由多个线程同时访问的表中。MySQL将错误地尝试创建信号量的两个竞争线程之一,因为关键约束意味着只有其中一个可以成功。IMO在Doctrine中存在一个缺陷,该缺陷不允许您安全地处理预期的MySQL错误。为什么因为一个INSERT语句有冲突而断开整个MySQL连接?
StampyCode 2014年

2
如果您尝试将异常记录到数据库中,app.exception_listener但是该异常(例如违反约束条件)关闭了连接,您还将看到此错误。
Lg102 '16

Answers:


24

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

我发现解决此问题的唯一方法是创建自己的DBAL Connection类,包装Doctrine类并提供异常处理(例如,在将异常弹出到EntityManager之前重试几次)。这有点骇人听闻,而且恐怕会导致事务环境中的某些不一致(即,我不确定如果查询失败是在事务中间时会发生什么情况)。

采用这种方式的示例配置为:

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的每种方法。使用封闭可以减轻那里的痛苦。


71

我的解决方案。

在进行任何检查之前:

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

所有实体将被保存。但这对于特定的班级或某些情况很方便。如果您有一些带有注入的entitymanager的服务,则该服务仍将关闭。


当di容器本身不可用时,这会更好。谢谢。
哈里KT

1
您可能还希望在第3个参数中传递$ this-> entityManager-> getEventManager()。
Medhat Gayed '17

34

Symfony 2.0

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

Symfony 2.1+

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

6
警告: resetEntityManager从Symfony 2.1开始不推荐使用。使用resetManager替代
弗朗西斯Casula旁

这还会重置工作单位吗?
2015年

@flu考虑到EntityManager类管理UnitOfWork类,我怀疑是这样。但是,我尚未对此进行测试,因此无法确定。
Ryall

26

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

在我的应用程序中,我正在运行多个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();

希望对您有所帮助:)


很棒的解释。我发现了类似的内容,并认为为您的回答做出贡献将是一件好事。非常感谢你。
Anjana Silva

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

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

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++;
    }               
  }                      
}


1

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

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

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


1

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

因此,检查日志是一个好主意


您能否提供有关第一个与第二个如何相关的其他信息?
乔治·诺维克,

1

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


0

我有这个问题。这是我固定的方式。

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

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



0

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

// 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已关闭。

在我的情况下,解决方案是只清除不同类型的Entity并保留$entityOne在EM下:

$this->em->clear(SomeEntityClass::class);

0

同样的问题,可以通过简单的代码重构解决。当必填字段为null时,有时会出现问题,然后再尝试重构代码。更好的工作流程可以解决问题。


-1

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

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

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

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

希望这对某人有帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.