EntityManager está cerrado

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

Después de recibir una excepción DBAL al insertar datos, EntityManager se cierra y no puedo volver a conectarlo.

Intenté así pero no conseguí una conexión.

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

¿Alguien tiene una idea de cómo volver a conectar?

Ueli
fuente
¿Por qué cierra el administrador de la entidad?
Jay Sheth
2
@JaySheth El administrador de la entidad podría cerrarse después de una excepción DBAL, o si está haciendo un EntityManager-> clear () antes de un flush. He visto a algunas personas que usan excepciones DBAL para ramificar el flujo de ejecución y luego terminan con un error cerrado de EntityManager. Si recibe este error, hay algo mal en el flujo de ejecución de su programa.
ILikeTacos
5
@AlanChavez - Recibo este error porque estoy usando Doctrine para escribir una bandera de semáforo en una tabla a la que acceden varios subprocesos simultáneamente. MySQL generará un error en uno de los dos subprocesos en competencia que intentan crear el semáforo, porque la restricción de clave significa que solo uno de ellos puede tener éxito. En mi opinión, hay una falla en Doctrine que no le permite manejar de manera segura los errores esperados de MySQL. ¿Por qué debería desconectarse toda la conexión MySQL porque una instrucción INSERT tiene un conflicto?
StampyCode
2
También verá este error si está intentando registrar excepciones en una base de datos en el app.exception_listenerpero la excepción (como una infracción de restricción) cerró la conexión.
Lg102

Respuestas:

24

Este es un problema muy complicado ya que, al menos para Symfony 2.0 y Doctrine 2.1, no es posible de ninguna manera volver a abrir EntityManager después de que se cierre.

La única forma que encontré para superar este problema es crear su propia clase de conexión DBAL, envolver la de Doctrine y proporcionar manejo de excepciones (por ejemplo, volver a intentarlo varias veces antes de enviar la excepción al EntityManager). Es un poco hacky y me temo que puede causar alguna inconsistencia en los entornos transaccionales (es decir, no estoy realmente seguro de lo que sucede si la consulta fallida está en medio de una transacción).

Una configuración de ejemplo para seguir de esta manera es:

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

La clase debería comenzar más o menos así:

namespace Your\DBAL;

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

Algo muy molesto es que debe anular cada método de conexión que proporciona su contenedor de manejo de excepciones. El uso de cierres puede aliviar un poco el dolor allí.

Aldo Stracquadanio
fuente
71

Mi solución.

Antes de hacer nada, compruebe:

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

Todas las entidades se guardarán. Pero es útil para una clase particular o algunos casos. Si tiene algunos servicios con entitymanager inyectado, aún estará cerrado.

Gregsparrow
fuente
esto es mucho mejor cuando el contenedor di en sí no está disponible. Gracias.
Hari KT
1
También puede querer pasar $ this-> entityManager-> getEventManager () en el tercer parámetro.
Medhat Gayed
34

Symfony 2.0 :

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

Symfony 2.1 o superior :

$em = $this->getDoctrine()->resetManager();
luisbg
fuente
6
ADVERTENCIA: resetEntityManager está en desuso desde Symfony 2.1. Úselo en su resetManagerlugar
Francesco Casula
¿Esto también restablece la unidad de trabajo?
gripe
@flu Teniendo en cuenta que la clase EntityManager gestiona la clase UnitOfWork, sospecho que lo haría. Sin embargo, no lo he probado, así que no puedo estar seguro.
Ryall
26

Así es como resolví la Doctrina "El EntityManager está cerrado". problema. Básicamente, cada vez que hay una excepción (es decir, una clave duplicada) o no proporcionar datos para una columna obligatoria, Doctrine cerrará Entity Manager. Si aún desea interactuar con la base de datos, debe restablecer el AdministradorresetManager() de entidades llamando al método mencionado por JGrinon .

En mi aplicación, estaba ejecutando varios consumidores RabbitMQ que estaban haciendo lo mismo: verificar si una entidad estaba allí en la base de datos, si es así, devolverla, si no, crearla y luego devolverla. En los pocos milisegundos entre verificar si esa entidad ya existía y crearla, otro consumidor hizo lo mismo y creó la entidad faltante, lo que hizo que el otro consumidor incurriera en una excepción de clave duplicada ( condición de carrera ).

Esto llevó a un problema de diseño de software. Básicamente, lo que estaba tratando de hacer era crear todas las entidades en una transacción. Esto puede parecer natural para la mayoría, pero definitivamente fue conceptualmente incorrecto en mi caso. Considere el siguiente problema: tuve que almacenar una entidad de partido de fútbol que tenía estas dependencias.

  • un grupo (por ejemplo, Grupo A, Grupo B ...)
  • una ronda (por ejemplo, semifinales ...)
  • un lugar (es decir, el estadio donde se lleva a cabo el partido)
  • un estado de partido (por ejemplo, medio tiempo, tiempo completo)
  • los dos equipos que juegan el partido
  • el partido en sí

Ahora bien, ¿por qué la creación del lugar debe realizarse en la misma transacción que el partido? Podría ser que acabo de recibir un nuevo lugar que no está en mi base de datos, así que primero tengo que crearlo. Pero también podría ser que ese lugar pueda albergar otro partido, por lo que es probable que otro consumidor intente crearlo al mismo tiempo. Entonces, lo que tuve que hacer fue crear todas las dependencias primero en transacciones separadas asegurándome de restablecer el administrador de entidades en una excepción de clave duplicada. Yo diría que todas las entidades que se encuentran al lado de la coincidencia podrían definirse como "compartidas" porque podrían ser parte de otras transacciones en otros consumidores. Algo que no se "comparte" allí es la coincidencia en sí que probablemente no será creada por dos consumidores al mismo tiempo.

Todo esto también llevó a otro problema. Si restableces Entity Manager, todos los objetos que has recuperado antes de restablecer son para Doctrine totalmente nuevos. ¡Entonces Doctrine no intentará ejecutar una ACTUALIZACIÓN en ellos, sino un INSERT ! Así que asegúrese de crear todas sus dependencias en transacciones lógicamente correctas y luego recupere todos sus objetos de la base de datos antes de configurarlos en la entidad de destino. Considere el siguiente código como ejemplo:

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

Así es como creo que debería hacerse.

$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 ayude :)

Francesco Casula
fuente
Fantástica explicación. Encontré algo similar y pensé que sería bueno contribuir a su respuesta. Muchas gracias.
Anjana Silva
17

Puede restablecer su 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
fuente
10

En Symfony 4.2+ tienes que usar el paquete:

composer require symfony/proxy-manager-bridge

de lo contrario, obtienes la excepción:

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

De lo que puede restablecer el entityManager de esta manera:

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();
    }
}
Sebastián Viereck
fuente
4

En controlador.

La excepción cierra el Entity Manager. Esto crea problemas para el inserto a granel. Para continuar, es necesario redefinirlo.

/** 
* @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
fuente
1

Por lo que vale, encontré que este problema estaba sucediendo en un comando de importación por lotes debido a un bucle try / catch que detecta un error SQL (con em->flush()) sobre el que no hice nada. En mi caso, fue porque estaba tratando de insertar un registro con una propiedad no anulable dejada como nula.

Por lo general, esto causaría que ocurriera una excepción crítica y que el comando o controlador se detuviera, pero solo estaba registrando este problema y continuaba. El error de SQL había provocado el cierre del administrador de entidades.

Verifique su dev.logarchivo en busca de errores de SQL tontos como este, ya que podría ser su culpa. :)

Adambean
fuente
1

Me enfrenté al mismo problema mientras probaba los cambios en Symfony 4.3.2

Bajé el nivel de registro a INFO

Y corrí la prueba de nuevo

Y el registrado mostró esto:

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

Esto significa que algún error en el código provoca:

Doctrine\ORM\ORMException: The EntityManager is closed.

Por tanto, es una buena idea comprobar el registro

Babak Bandpey
fuente
¿Podría proporcionar información adicional sobre cómo se relaciona el primero con el segundo?
George Novik
1

Symfony v4.1.6

Doctrina v2.9.0

Proceso de inserción de duplicados en un repositorio

  1. Obtenga acceso a un registro en su repositorio


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

  1. Envuelva el código de riesgo en la transacción y restablezca el administrador en caso de excepción


    //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
fuente
0

Tuve este problema. Así es como lo arreglé.

La conexión parece cerrarse al intentar vaciar o persistir. Intentar reabrirlo es una mala elección porque crea nuevos problemas. Intenté entender por qué se cerró la conexión y descubrí que estaba haciendo demasiadas modificaciones antes de persistir.

persist () anteriormente resolvió el problema.

usuario3046563
fuente
0

Intente usar:

$em->getConnection()->[setNestTransactionsWithSavepoints][1](true);

antes de iniciar una transacción.

En el Connection::rollbackmétodo, comprueba la nestTransactionsWithSavepointspropiedad.

zechim
fuente
3
¿Puedes ampliar esto?
paul.ago
0

Este es un problema realmente antiguo, pero tuve un problema similar. Estaba haciendo algo como esto:

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

El problema fue que despejar todas las entidades, incluida la primera, y arrojar el error El EntityManager está cerrado.

En mi caso, la solución fue simplemente aclarar un tipo distinto de entidad y dejarlo $entityOnetodavía en EM:

$this->em->clear(SomeEntityClass::class);
Nikola Loncar
fuente
0

Mismo problema, resuelto con una simple refactorización de código. El problema está presente en algún momento cuando un campo obligatorio es nulo, antes de hacer nada, intente refactorizar su código. Un mejor flujo de trabajo puede resolver el problema.

Axel Briche
fuente
-1

Tuve el mismo error al usar Symfony 5 / Doctrine 2. Uno de mis campos fue nombrado usando una palabra reservada de MySQL "orden", lo que provocó una DBALException. Cuando desee usar una palabra reservada, debe escapar de su nombre usando ticks inversos. En forma de anotación:

@ORM\Column(name="`order`", type="integer", nullable=false)
Niño de la luna
fuente
-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
fuente
-2

Me enfrenté al mismo problema. Después de mirar varios lugares, aquí es cómo lo manejé.

//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 esto ayude a alguien!

Mayank Tiwari
fuente