[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?
app.exception_listener
mas a exceção (como uma violação de restrição) fechou a conexão.Respostas:
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.
fonte
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.
fonte
Symfony 2.0 :
$em = $this->getDoctrine()->resetEntityManager();
Symfony 2.1+ :
$em = $this->getDoctrine()->resetManager();
fonte
resetEntityManager
está obsoleto desde o Symfony 2.1. Use emresetManager
vez dissoFoi 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.
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 :)
fonte
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();
fonte
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(); } }
fonte
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++; } } }
fonte
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.log
há algum erro bobo de SQL em seu arquivo, pois a culpa pode ser sua. :)fonte
Eu encontrei um artigo interessante sobre este problema
if (!$entityManager->isOpen()) { $entityManager = $entityManager->create( $entityManager->getConnection(), $entityManager->getConfiguration()); }
Doctrine 2 Exception EntityManager está fechado
fonte
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:
Portanto, é uma boa ideia verificar o log
fonte
Symfony v4.1.6
Doctrine v2.9.0
Processo de inserção de duplicatas em um repositório
//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(); } }
fonte
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.
fonte
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
$entityOne
ainda sob EM:$this->em->clear(SomeEntityClass::class);
fonte
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.
fonte
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)
fonte
// 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();
fonte
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!
fonte