EntityManager viene chiuso


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

Dopo aver ricevuto un'eccezione DBAL durante l'inserimento dei dati, EntityManager si chiude e non riesco a ricollegarlo.

Ho provato in questo modo ma non ho ottenuto una connessione.

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

Qualcuno ha idea di come riconnettersi?


Perché il gestore entità chiude?
Jay Sheth

2
@JaySheth Il gestore entità potrebbe chiudersi dopo un'eccezione DBAL, o se stai facendo un EntityManager-> clear () prima di un flush. Ho visto alcune persone che utilizzano eccezioni DBAL per diramare il flusso di esecuzione e quindi finire con un errore di chiusura di EntityManager. Se ricevi questo errore, c'è qualcosa di sbagliato nel flusso di esecuzione del tuo programma.
ILikeTacos

5
@AlanChavez - Ricevo questo errore perché sto usando Doctrine per scrivere un indicatore semaforo in una tabella a cui accedono più thread contemporaneamente. MySQL sbaglierà uno dei due thread concorrenti nel tentativo di creare il semaforo, perché il vincolo di chiave significa che solo uno di essi può avere successo. IMO c'è un difetto in Doctrine che non ti consente di gestire in modo sicuro gli errori MySQL previsti . Perché l'intera connessione MySQL dovrebbe essere disconnessa perché un'istruzione INSERT ha un conflitto?
StampyCode

2
Vedrai questo errore anche se stai tentando di registrare le eccezioni in un database nel app.exception_listenerma l'eccezione (come una violazione di vincoli) ha chiuso la connessione.
Lg102

Risposte:


25

Questo è un problema molto complicato poiché, almeno per Symfony 2.0 e Doctrine 2.1, non è possibile in alcun modo riaprire EntityManager dopo la chiusura.

L'unico modo che ho trovato per superare questo problema è creare la tua classe di connessione DBAL, racchiudere quella di Doctrine e fornire la gestione delle eccezioni (ad es. Riprovare più volte prima di far apparire l'eccezione in EntityManager). È un po 'hacky e temo che possa causare qualche incoerenza negli ambienti transazionali (cioè non sono veramente sicuro di cosa succede se la query fallita è nel mezzo di una transazione).

Una configurazione di esempio da seguire in questo modo è:

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 classe dovrebbe iniziare più o meno così:

namespace Your\DBAL;

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

Una cosa molto fastidiosa è che devi sovrascrivere ogni metodo di connessione fornendo il tuo wrapper per la gestione delle eccezioni. L'uso di chiusure può alleviare un po 'di dolore lì.


74

La mia soluzione.

Prima di fare qualsiasi cosa controllare:

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

Tutte le entità verranno salvate. Ma è utile per classi particolari o alcuni casi. Se hai alcuni servizi con entitymanager iniettato, sarà comunque chiuso.


questo è molto meglio quando il contenitore stesso non è disponibile. Grazie.
Hari KT

1
potresti anche voler passare $ this-> entityManager-> getEventManager () nel terzo parametro.
Medhat Gayed

34

Symfony 2.0 :

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

Symfony 2.1+ :

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

6
ATTENZIONE: resetEntityManager è deprecato a partire da Symfony 2.1. Usa resetManagerinvece
Francesco Casula

Questo ripristina anche l'unità di lavoro?
influenza

@flu Considerando che la classe EntityManager gestisce la classe UnitOfWork, sospetto che lo farebbe. Tuttavia, non l'ho testato, quindi non posso esserne sicuro.
Ryall

28

Ecco come ho risolto la dottrina "EntityManager è chiuso". problema. Fondamentalmente ogni volta che c'è un'eccezione (cioè chiave duplicata) o non fornire dati per una colonna obbligatoria farà sì che Doctrine chiuda Entity Manager. Se vuoi comunque interagire con il database devi reimpostare l'Entity Manger chiamando il resetManager()metodo menzionato da JGrinon .

Nella mia applicazione stavo eseguendo più consumatori RabbitMQ che stavano facendo tutti la stessa cosa: controllare se un'entità era presente nel database, in caso affermativo restituirla, in caso contrario crearla e quindi restituirla. Nei pochi millisecondi tra il controllo se quell'entità esisteva già e la sua creazione, un altro consumatore ha fatto lo stesso e ha creato l'entità mancante facendo incorrere l'altro consumatore in un'eccezione di chiave duplicata ( race condition ).

Ciò ha portato a un problema di progettazione del software. Fondamentalmente quello che stavo cercando di fare era creare tutte le entità in una transazione. Questo può sembrare naturale per la maggior parte, ma nel mio caso era decisamente sbagliato concettualmente. Considera il seguente problema: dovevo archiviare un'entità Partita di calcio che aveva queste dipendenze.

  • un gruppo (es. gruppo A, gruppo B ...)
  • un round (es. semifinali ...)
  • un luogo (cioè lo stadio in cui si svolge la partita)
  • uno stato della partita (ad esempio metà tempo, tempo pieno)
  • le due squadre che giocano la partita
  • la partita stessa

Ora, perché la creazione del luogo dovrebbe essere nella stessa transazione della partita? Potrebbe essere che ho appena ricevuto un nuovo locale che non è nel mio database, quindi devo prima crearlo. Ma potrebbe anche essere che quel luogo possa ospitare un'altra partita, quindi un altro consumatore probabilmente proverà a crearlo allo stesso tempo. Quindi quello che dovevo fare era creare prima tutte le dipendenze in transazioni separate assicurandomi di reimpostare il gestore entità in un'eccezione chiave duplicata. Direi che tutte le entità presenti accanto alla corrispondenza potrebbero essere definite "condivise" perché potrebbero potenzialmente far parte di altre transazioni in altri consumatori. Qualcosa che non è "condiviso" è la corrispondenza stessa che probabilmente non verrà creata da due consumatori contemporaneamente.

Tutto ciò ha portato anche a un altro problema. Se ripristini Entity Manager, tutti gli oggetti che hai recuperato prima del ripristino sono per Doctrine completamente nuovi. Quindi Doctrine non proverà a eseguire un AGGIORNAMENTO su di essi ma un INSERT ! Quindi assicurati di creare tutte le tue dipendenze in transazioni logicamente corrette e quindi recuperare tutti i tuoi oggetti dal database prima di impostarli sull'entità di destinazione. Considera il codice seguente come esempio:

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

Quindi questo è come penso che dovrebbe essere fatto.

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

Spero possa essere d'aiuto :)


Fantastica spiegazione. Ho trovato qualcosa di simile e ho pensato che sarebbe stato carino contribuire alla tua risposta. Grazie mille.
Anjana Silva

17

Puoi resettare il tuo EM così

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

In Symfony 4.2+ devi usare il pacchetto:

composer require symfony/proxy-manager-bridge

altrove si ottiene l'eccezione:

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

Quindi puoi reimpostare entityManager in questo modo:

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

Nel controller.

L'eccezione chiude l'Entity Manager. Ciò crea problemi per l'inserimento di massa. Per continuare, è necessario ridefinirlo.

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

2

Per quello che vale, ho scoperto che questo problema si verificava in un comando di importazione batch a causa di un ciclo try / catch che rileva un errore SQL (con em->flush()) per il quale non ho fatto nulla. Nel mio caso è stato perché stavo cercando di inserire un record con una proprietà non annullabile lasciata come null.

In genere ciò causerebbe un'eccezione critica e l'arresto del comando o del controller, ma stavo solo registrando questo problema e proseguendo. L'errore SQL aveva causato la chiusura del gestore entità.

Controlla il tuo dev.logfile per eventuali errori SQL stupidi come questo in quanto potrebbe essere colpa tua. :)



1

Ho riscontrato lo stesso problema durante il test delle modifiche in Symfony 4.3.2

Ho abbassato il livello di registro a INFO

E ho eseguito di nuovo il test

E il registro ha mostrato questo:

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

Ciò significa che qualche errore nel codice causa:

Doctrine\ORM\ORMException: The EntityManager is closed.

Quindi è una buona idea controllare il registro


Potreste fornire ulteriori informazioni su come il primo è correlato al secondo?
George Novik

1

Symfony v4.1.6

Doctrine v2.9.0

Processo di inserimento dei duplicati in un repository

  1. Ottieni l'accesso a un registro nel tuo repository


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

  1. Avvolgi il codice rischioso nella transazione e ripristina il gestore in caso di eccezione


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

Ho avuto questo problema. Ecco come l'ho risolto.

La connessione sembra chiudersi durante il tentativo di svuotamento o persistenza. Tentare di riaprirlo è una cattiva scelta perché crea nuovi problemi. Ho provato a capire perché la connessione è stata chiusa e ho scoperto che stavo facendo troppe modifiche prima che persistesse.

persist () in precedenza ha risolto il problema.


0

Questo è davvero un vecchio problema, ma ho appena avuto un problema simile. Stavo facendo qualcosa del genere:

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

Il problema era che chiaramente scollega tutte le entità inclusa la prima e genera l'errore EntityManager è chiuso.

Nel mio caso la soluzione era di fare chiarezza su un tipo distinto di entità e lasciare $entityOneancora in EM:

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

La chiamata a Doctrine \ ORM \ EntityManager :: clear () con qualsiasi argomento per cancellare entità specifiche è deprecata e non sarà supportata in Doctrine ORM 3.0
Foued MOUSSI

0

Stesso problema, risolto con un semplice refactoring del codice. Il problema è presente a volte quando un campo obbligatorio è nullo, prima di fare qualsiasi cosa, prova a rifattorizzare il tuo codice. Un flusso di lavoro migliore può risolvere il problema.


-1

Ho avuto lo stesso errore usando Symfony 5 / Doctrine 2. Uno dei miei campi è stato denominato usando una parola riservata MySQL "order", causando un'eccezione DBALE. Quando vuoi usare una parola riservata, devi sfuggire al suo nome usando i segni di spunta. In forma di annotazione:

@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

Ho affrontato lo stesso problema. Dopo aver esaminato diversi punti, ecco come l'ho affrontato.

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

Spero che questo aiuti qualcuno!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.