EntityManager jest zamknięty

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

Po otrzymaniu wyjątku DBAL podczas wstawiania danych EntityManager zostaje zamknięty i nie mogę go ponownie połączyć.

Próbowałem w ten sposób, ale nie udało mi się uzyskać połączenia.

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

Czy ktoś ma pomysł, jak się ponownie połączyć?

Ueli
źródło
Dlaczego zarządzający podmiotem zamyka się?
Jay Sheth,
2
@JaySheth Menedżer encji może zostać zamknięty po wyjątku DBAL lub jeśli wykonujesz EntityManager-> clear () przed opróżnieniem. Widziałem niektórych ludzi używających wyjątków DBAL do rozgałęziania przepływu wykonywania, a następnie kończących się zamkniętym błędem EntityManager. Jeśli otrzymujesz ten błąd, oznacza to, że coś jest nie tak w przepływie wykonywania programu.
ILikeTacos
5
@AlanChavez - Otrzymuję ten błąd, ponieważ używam Doctrine do zapisania flagi semafora do tabeli, do której ma dostęp wiele wątków jednocześnie. MySQL zgłosi błąd w jednym z dwóch konkurujących wątków próbujących utworzyć semafor, ponieważ ograniczenie klucza oznacza, że ​​tylko jeden z nich może się powieść. IMO zawiera lukę w Doctrine, która nie pozwala na bezpieczną obsługę oczekiwanych błędów MySQL. Dlaczego całe połączenie MySQL powinno być rozłączane, ponieważ jedna instrukcja INSERT powoduje konflikt?
StampyCode
2
Ten błąd zostanie również wyświetlony, jeśli próbujesz zarejestrować wyjątki w bazie danych, app.exception_listenerale wyjątek (na przykład naruszenie ograniczenia) zamknął połączenie.
Lg102

Odpowiedzi:

24

Jest to bardzo trudny problem, ponieważ przynajmniej w przypadku Symfony 2.0 i Doctrine 2.1 nie jest w żaden sposób możliwe ponowne otwarcie EntityManagera po jego zamknięciu.

Jedynym sposobem, w jaki znalazłem rozwiązanie tego problemu, jest utworzenie własnej klasy DBAL Connection, zawinięcie klasy Doctrine i zapewnienie obsługi wyjątków (np. Ponawianie kilkukrotnej próby przed wysłaniem wyjątku do EntityManager). Jest to trochę zepsute i obawiam się, że może powodować niespójność w środowiskach transakcyjnych (tj. Nie jestem do końca pewien, co się stanie, jeśli niepowodzenie zapytania jest w trakcie transakcji).

Przykładowa konfiguracja, którą można przejść w ten sposób:

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

Zajęcia powinny zaczynać się mniej więcej tak:

namespace Your\DBAL;

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

Bardzo irytujące jest to, że musisz zastąpić każdą metodę połączenia, która zapewnia opakowanie do obsługi wyjątków. Używanie zamknięć może złagodzić ból.

Aldo Stracquadanio
źródło
71

Moje rozwiązanie.

Zanim cokolwiek zrobisz, sprawdź:

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

Wszystkie podmioty zostaną zapisane. Ale jest to przydatne dla określonej klasy lub niektórych przypadków. Jeśli masz jakieś usługi z wstrzykniętym menadżerem uprawnień, nadal będą one zamknięte.

Gregsparrow
źródło
jest to o wiele lepsze, gdy sam pojemnik di jest niedostępny. Dziękuję Ci.
Hari KT
1
możesz także chcieć przekazać $ this-> entityManager-> getEventManager () w trzecim parametrze.
Medhat Gayed
34

Symfony 2.0 :

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

Symfony 2.1+ :

$em = $this->getDoctrine()->resetManager();
luisbg
źródło
6
OSTRZEŻENIE: resetEntityManager jest przestarzałe od Symfony 2.1. Użyj resetManagerzamiast tego
Francesco Casula
Czy to również powoduje zresetowanie jednostki pracy?
grypa
@flu Biorąc pod uwagę, że klasa EntityManager zarządza klasą UnitOfWork, podejrzewam, że tak. Jednak nie testowałem tego, więc nie mogę być pewien.
Ryall
26

W ten sposób rozwiązałem Doktrynę „EntityManager jest zamknięty”. kwestia. Zasadniczo za każdym razem, gdy wystąpi wyjątek (np. Zduplikowany klucz) lub nie podanie danych dla obowiązkowej kolumny, Doctrine zamknie Entity Manager. Jeśli nadal chcesz współdziałać z bazą danych, musisz zresetować Entity Manger, wywołując resetManager()metodę, o której wspomniał JGrinon .

W mojej aplikacji pracowałem z wieloma konsumentami RabbitMQ, którzy robili to samo: sprawdzanie, czy jednostka jest w bazie danych, jeśli tak, zwróć ją, jeśli nie, utwórz ją i zwróć. W ciągu kilku milisekund między sprawdzeniem, czy ta jednostka już istniała, a jej utworzeniem, inny konsument zrobił to samo i utworzył brakującą jednostkę, powodując, że drugi konsument poniósł zduplikowany klucz ( stan wyścigu ).

Doprowadziło to do problemu z projektowaniem oprogramowania. Zasadniczo próbowałem stworzyć wszystkie jednostki w jednej transakcji. Dla większości może to wydawać się naturalne, ale w moim przypadku było to zdecydowanie błędne koncepcyjnie. Rozważmy następujący problem: musiałem zapisać obiekt Football Match, który miał te zależności.

  • grupa (np. Grupa A, Grupa B ...)
  • runda (np. półfinały ...)
  • miejsce (tj. stadion, na którym odbywa się mecz)
  • status meczu (np. połowa, pełny etat)
  • dwie drużyny grające w meczu
  • sam mecz

Teraz, dlaczego utworzenie miejsca powinno odbywać się w tej samej transakcji co mecz? Możliwe, że właśnie otrzymałem nowe miejsce, którego nie ma w mojej bazie danych, więc muszę je najpierw utworzyć. Może się jednak zdarzyć, że to miejsce będzie gospodarzem innego dopasowania, więc inny konsument prawdopodobnie spróbuje również je utworzyć w tym samym czasie. Musiałem więc najpierw utworzyć wszystkie zależności w oddzielnych transakcjach, upewniając się, że resetowałem menedżera encji w zduplikowanym kluczu. Powiedziałbym, że wszystkie podmioty znajdujące się tam obok dopasowania można zdefiniować jako „współdzielone”, ponieważ potencjalnie mogłyby być częścią innych transakcji na innych konsumentach. Coś, co nie jest tam „udostępniane”, to samo dopasowanie, które prawdopodobnie nie zostanie utworzone przez dwóch konsumentów jednocześnie.

Wszystko to doprowadziło również do innego problemu. Jeśli zresetujesz Entity Manager, wszystkie obiekty, które odzyskałeś przed zresetowaniem, są dla Doctrine całkowicie nowe. Tak więc Doctrine nie będzie próbował uruchomić na nich AKTUALIZACJI, ale WSTAWIĆ ! Dlatego upewnij się, że utworzyłeś wszystkie zależności w logicznie poprawnych transakcjach, a następnie odzyskasz wszystkie obiekty z bazy danych przed ustawieniem ich na jednostkę docelową. Rozważmy następujący kod jako przykład:

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

Więc myślę, że tak powinno być.

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

Mam nadzieję, że to pomoże :)

Francesco Casula
źródło
Fantastyczne wyjaśnienie. Znalazłem coś podobnego i pomyślałem, że byłoby miło przyczynić się do twojej odpowiedzi. Dziękuję Ci bardzo.
Anjana Silva
17

Możesz zresetować EM tak

// 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
źródło
10

W Symfony 4.2+ musisz skorzystać z pakietu:

composer require symfony/proxy-manager-bridge

w przeciwnym razie otrzymasz wyjątek:

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

Następnie możesz zresetować entityManager w następujący sposób:

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
źródło
4

W kontrolerze.

Wyjątek zamyka Entity Manager. To sprawia problemy przy wkładaniu zbiorczym. Aby kontynuować, musisz go przedefiniować.

/** 
* @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
źródło
2

Znalazłem interesujący artykuł dotyczący tego problemu

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

EntityManager w Doctrine 2 Exception jest zamknięty

stephan.mada
źródło
1

Co jest warte, zauważyłem, że ten problem występuje w poleceniu importu wsadowego z powodu pętli try / catch przechwytującej błąd SQL (z em->flush()), z którym nic nie zrobiłem. W moim przypadku było tak, ponieważ próbowałem wstawić rekord z właściwością niepodlegającą wartości null pozostawioną jako null.

Zwykle powodowałoby to wystąpienie krytycznego wyjątku i zatrzymanie polecenia lub kontrolera, ale zamiast tego po prostu rejestrowałem ten problem i kontynuowałem. Błąd SQL spowodował zamknięcie menedżera encji.

Sprawdź, czy w dev.logpliku nie ma żadnych głupich błędów SQL, takich jak ten, ponieważ może to być Twoja wina. :)

Adambean
źródło
1

Napotkałem ten sam problem podczas testowania zmian w Symfony 4.3.2

Obniżyłem poziom dziennika do INFO

I ponownie uruchom test

A zalogowany pokazał to:

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

Oznacza to, że jakiś błąd w kodzie powoduje:

Doctrine\ORM\ORMException: The EntityManager is closed.

Warto więc sprawdzić dziennik

Babak Bandpey
źródło
Czy możesz podać dodatkowe informacje o tym, jaki jest związek między pierwszym a drugim?
George Novik
1

Symfony v4.1.6

Doctrine v2.9.0

Proces wstawiania duplikatów do repozytorium

  1. Uzyskaj dostęp do rejestru w swoim repozytorium


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

  1. Umieść ryzykowny kod w transakcji i zresetuj menedżera w przypadku wyjątku


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

Aleksandra Szewczenki
źródło
0

Miałem ten problem. Tak to naprawiłem.

Wydaje się, że połączenie zostaje zamknięte podczas próby opróżnienia lub utrwalenia. Próba ponownego otwarcia jest złym wyborem, ponieważ stwarza nowe problemy. Próbowałem zrozumieć, dlaczego połączenie zostało zamknięte i stwierdziłem, że robiłem zbyt wiele modyfikacji, zanim utrwaliłem się.

Persist () wcześniej rozwiązał problem.

user3046563
źródło
0

To naprawdę stary problem, ale miałem podobny problem. Robiłem coś takiego:

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

Problem polegał na tym, że wyczyść odłącz wszystkie jednostki, w tym pierwszą, i zgłoś błąd . EntityManager jest zamknięty.

W moim przypadku rozwiązaniem było po prostu wyczyszczenie odrębnego typu podmiotu i pozostawienie $entityOnenadal pod EM:

$this->em->clear(SomeEntityClass::class);
Nikola Loncar
źródło
0

Ten sam problem, rozwiązany za pomocą prostej refaktoryzacji kodu. Problem pojawia się czasem, gdy wymagane pole ma wartość null, przed wykonaniem jakiejkolwiek operacji spróbuj zrefaktoryzować kod. Lepszy przepływ pracy może rozwiązać problem.

Axel Briche
źródło
-1

Miałem ten sam błąd podczas korzystania z Symfony 5 / Doctrine 2. Jedno z moich pól zostało nazwane przy użyciu zastrzeżonego słowa MySQL „order”, co spowodowało wyjątek DBALException. Jeśli chcesz użyć zastrzeżonego słowa, musisz uciec od jego nazwy za pomocą zwrotów. W formie adnotacji:

@ORM\Column(name="`order`", type="integer", nullable=false)
Księżycowe dziecko
źródło
-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();
Jewgienij Malyshkin
źródło
-2

Miałem ten sam problem. Po obejrzeniu kilku miejsc, oto jak sobie z tym poradziłem.

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

Mam nadzieję, że to komuś pomoże!

Mayank Tiwari
źródło