O EntityManager está fechado

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

Depois que obtenho uma exceção DBAL ao inserir dados, o EntityManager fecha e não consigo reconectá-lo.

Tentei assim, mas não consegui conexão.

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

Alguém tem uma ideia de como reconectar?

Ueli
fonte
Por que o gerenciador de entidade fecha?
Jay Sheth
2
@JaySheth O gerenciador de entidade pode fechar após uma exceção DBAL, ou se você estiver executando um EntityManager-> clear () antes de uma descarga. Já vi algumas pessoas usando exceções DBAL para ramificar o fluxo de execução e, em seguida, terminando com um erro fechado EntityManager. Se você está recebendo esse erro, há algo errado no fluxo de execução do seu programa.
ILikeTacos
5
@AlanChavez - Estou recebendo este erro porque estou usando o Doctrine para escrever um sinalizador de semáforo em uma tabela que está sendo acessada por vários threads simultaneamente. O MySQL irá errar um dos dois threads concorrentes ao tentar criar o semáforo, porque a restrição de chave significa que apenas um deles pode ter sucesso. IMO, há uma falha no Doctrine que não permite que você trate com segurança os erros esperados do MySQL. Por que toda a conexão MySQL deve ser desconectada porque uma instrução INSERT está em conflito?
StampyCode
2
Você também verá esse erro se estiver tentando registrar exceções em um banco de dados no, app.exception_listenermas a exceção (como uma violação de restrição) fechou a conexão.
Lg102

Respostas:

25

Este é um problema muito complicado, pois, pelo menos para Symfony 2.0 e Doctrine 2.1, não é possível de forma alguma reabrir o EntityManager após ele ser fechado.

A única maneira que encontrei de superar esse problema é criar sua própria classe DBAL Connection, agrupar a classe Doctrine e fornecer tratamento de exceção (por exemplo, tentar várias vezes antes de enviar a exceção para o EntityManager). É um pouco hacky e receio que possa causar alguma inconsistência em ambientes transacionais (ou seja, não tenho certeza do que acontece se a consulta com falha estiver no meio de uma transação).

Um exemplo de configuração a ser seguido é:

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

A aula deve começar mais ou menos assim:

namespace Your\DBAL;

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

Uma coisa muito chata é que você tem que substituir cada método do Connection fornecendo seu wrapper de tratamento de exceções. Usar fechos pode aliviar um pouco a dor.

Aldo Stracquadanio
fonte
74

Minha solução.

Antes de fazer qualquer coisa, verifique:

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

Todas as entidades serão salvas. Mas é útil para uma classe particular ou alguns casos. Se você tiver alguns serviços com gerenciador de entidade injetado, ele ainda estará fechado.

Gregsparrow
fonte
isso é muito melhor quando o próprio contêiner di não está disponível. Obrigado.
Hari KT
1
você também pode querer passar $ this-> entityManager-> getEventManager () no terceiro parâmetro.
Medhat Gayed
34

Symfony 2.0 :

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

Symfony 2.1+ :

$em = $this->getDoctrine()->resetManager();
luisbg
fonte
6
AVISO: resetEntityManager está obsoleto desde o Symfony 2.1. Use em resetManagervez disso
Francesco Casula
Isso também redefine a unidade de trabalho?
gripe
@flu Considerando que a classe EntityManager gerencia a classe UnitOfWork, eu suspeito que sim. No entanto, não testei isso, então não posso ter certeza.
Ryall
28

Foi assim que resolvi a Doutrina "O EntityManager está fechado." questão. Basicamente, toda vez que houver uma exceção (ou seja, chave duplicada) ou o não fornecimento de dados para uma coluna obrigatória fará com que o Doctrine feche o Entity Manager. Se você ainda deseja interagir com o banco de dados, você deve redefinir o Entity Manager chamando o resetManager()método mencionado por JGrinon .

No meu aplicativo eu estava executando vários consumidores RabbitMQ que estavam todos fazendo a mesma coisa: verificar se havia uma entidade no banco de dados, se sim devolvia, senão criava e depois devolvia. Nos poucos milissegundos entre verificar se essa entidade já existia e criá-la, outro consumidor fez o mesmo e criou a entidade ausente, fazendo com que o outro consumidor incorresse em uma exceção de chave duplicada ( condição de corrida ).

Isso levou a um problema de design de software. Basicamente, o que eu estava tentando fazer era criar todas as entidades em uma transação. Isso pode parecer natural para a maioria, mas definitivamente era conceitualmente errado no meu caso. Considere o seguinte problema: eu tive que armazenar uma entidade de jogo de futebol que tinha essas dependências.

  • um grupo (por exemplo, Grupo A, Grupo B ...)
  • uma rodada (por exemplo, semifinais ...)
  • um local (ou seja, estádio onde a partida está acontecendo)
  • um status de jogo (por exemplo, intervalo, tempo integral)
  • as duas equipes jogando a partida
  • o jogo em si

Agora, por que a criação do local deve estar na mesma transação da partida? Pode ser que acabei de receber um novo local que não está no meu banco de dados, então tenho que criá-lo primeiro. Mas também pode ser que aquele local hospede outro jogo, de modo que outro consumidor provavelmente tentará criá-lo ao mesmo tempo. Então, o que eu tive que fazer foi criar todas as dependências primeiro em transações separadas, certificando-me de que estava redefinindo o gerenciador de entidade em uma exceção de chave duplicada. Eu diria que todas as entidades ali ao lado da correspondência podem ser definidas como "compartilhadas" porque podem potencialmente fazer parte de outras transações em outros consumidores. Algo que não é "compartilhado" ali é a própria correspondência que provavelmente não será criada por dois consumidores ao mesmo tempo.

Tudo isso também levou a outro problema. Se você resetar o Entity Manager, todos os objetos que você recuperou antes de resetar são para o Doctrine totalmente novos. Portanto, o Doctrine não tentará executar um UPDATE neles, mas sim um INSERT ! Portanto, certifique-se de criar todas as suas dependências em transações logicamente corretas e, em seguida, recuperar todos os seus objetos do banco de dados antes de defini-los para a entidade de destino. Considere o seguinte código como exemplo:

$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.
 */

Então é assim que eu acho que deveria ser feito.

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

Espero que ajude :)

Francesco Casula
fonte
Explicação fantástica. Encontrei algo semelhante e achei que seria bom contribuir com a sua resposta. Muito obrigado.
Anjana Silva
17

Você pode redefinir seu EM para

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

No Symfony 4.2+, você deve usar o pacote:

composer require symfony/proxy-manager-bridge

caso contrário, você obtém a exceção:

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

Então, você pode redefinir o entityManager assim:

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();
    }
}
Sebastian viereck
fonte
4

No controlador.

A exceção fecha o Entity Manager. Isso cria problemas para a inserção em massa. Para continuar, é preciso redefini-lo.

/** 
* @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++;
    }               
  }                      
}
Vadim
fonte
2

Pelo que vale a pena, descobri que esse problema estava acontecendo em um comando de importação em lote por causa de um loop try / catch capturando um erro de SQL (com em->flush()) sobre o qual eu não fiz nada. No meu caso, foi porque eu estava tentando inserir um registro com uma propriedade não anulável deixada como nula.

Normalmente, isso faria com que uma exceção crítica acontecesse e o comando ou controlador parasse, mas eu estava apenas registrando o problema e continuando. O erro de SQL fez com que o gerenciador de entidades fechasse.

Verifique se dev.loghá algum erro bobo de SQL em seu arquivo, pois a culpa pode ser sua. :)

Adambiano
fonte
2

Eu encontrei um artigo interessante sobre este problema

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

Doctrine 2 Exception EntityManager está fechado

stephan.mada
fonte
1

Eu enfrentei o mesmo problema ao testar as mudanças no Symfony 4.3.2

Baixei o nível de registro para INFO

E fiz o teste novamente

E o logado mostrou isso:

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?"} []

Isso significa que algum erro no código causa o:

Doctrine\ORM\ORMException: The EntityManager is closed.

Portanto, é uma boa ideia verificar o log

Babak Bandpey
fonte
Você poderia fornecer informações adicionais sobre como o primeiro está relacionado ao segundo?
George Novik
1

Symfony v4.1.6

Doctrine v2.9.0

Processo de inserção de duplicatas em um repositório

  1. Obtenha acesso a um registro em seu repo


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

  1. Envolva o código de risco na transação e reinicie o gerenciador em caso de exceção


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

Alexandr Shevchenko
fonte
0

Eu tive esse problema. Foi assim que eu consertei.

A conexão parece fechar ao tentar liberar ou persistir. Tentar reabri-lo é uma escolha ruim porque cria novos problemas. Tentei entender porque a conexão foi fechada e descobri que estava fazendo muitas modificações antes de persistir.

persist () resolveu o problema anteriormente.

user3046563
fonte
0

Este é um problema muito antigo, mas eu apenas tive um problema semelhante. Eu estava fazendo algo assim:

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

O problema era que desanexar claramente todas as entidades, incluindo a primeira e lançar o erro O EntityManager é fechado.

No meu caso, a solução foi apenas limpar um tipo distinto de Entidade e deixar $entityOneainda sob EM:

$this->em->clear(SomeEntityClass::class);
Nikola Loncar
fonte
Chamar Doctrine \ ORM \ EntityManager :: clear () com quaisquer argumentos para limpar entidades específicas está obsoleto e não será suportado no Doctrine ORM 3.0
Foued MOUSSI
0

Mesmo problema, resolvido com uma simples refatoração de código. O problema está às vezes presente quando um campo obrigatório é nulo, antes de fazer anithing, tente refatorar seu código. Um fluxo de trabalho melhor pode resolver o problema.

Axel Briche
fonte
-1

Eu tive o mesmo erro ao usar o Symfony 5 / Doctrine 2. Um dos meus campos foi nomeado usando uma palavra reservada do MySQL "pedido", causando uma DBALException. Quando você quiser usar uma palavra reservada, você deve escapar de seu nome usando back-ticks. Em forma de anotação:

@ORM\Column(name="`order`", type="integer", nullable=false)
Filho da noite
fonte
-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();
Evgeny Malyshkin
fonte
-2

Eu enfrentei o mesmo problema. Depois de examinar vários lugares, aqui está como lidou com isso.

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

Espero que isso ajude alguém!

Mayank Tiwari
fonte