EntityManager закрито


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

Після отримання винятку DBAL при вставці даних EntityManager закривається, і я не можу повторно підключити його.

Я спробував так, але це не отримало з'єднання.

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

У когось є ідея, як відновити зв’язок?


Чому менеджер організації закривається?
Jay Sheth

2
@JaySheth Менеджер сутності може закритись після винятку DBAL або якщо ви робите EntityManager-> clear () перед змивом. Я бачив, як деякі люди використовують винятки DBAL для розгалуження потоку виконання, а потім закінчують закритою помилкою EntityManager. Якщо ви отримуєте цю помилку, у вашій програмі щось не так у процесі виконання.
ILikeTacos

5
@AlanChavez - Я отримую цю помилку, оскільки використовую Doctrine для написання прапора семафору до таблиці, до якої одночасно отримують доступ декілька потоків. MySQL помилиться в одному з двох конкуруючих потоків, намагаючись створити семафор, оскільки обмеження ключа означає, що лише один із них може досягти успіху. У IMO є недолік у Doctrine, який не дозволяє безпечно обробляти очікувані помилки MySQL. Чому все з'єднання MySQL слід від'єднати, оскільки один оператор INSERT має конфлікт?
StampyCode

2
Ви також побачите цю помилку, якщо намагаєтеся зареєструвати винятки в базі даних, app.exception_listenerале виняток (наприклад, порушення обмеження) закрив з'єднання.
Lg102,

Відповіді:


24

Це дуже складна проблема, оскільки, принаймні для Symfony 2.0 та Doctrine 2.1, жодним чином неможливо відкрити EntityManager після його закриття.

Єдиний спосіб, який я знайшов, щоб подолати цю проблему, - це створити власний клас DBAL Connection, обернути доктрину та забезпечити обробку винятків (наприклад, повторити спробу кілька разів, перш ніж виводити виняток до EntityManager). Це трохи хакерсько, і я боюся, що це може спричинити деяку невідповідність у середовищах транзакцій (тобто я не дуже впевнений у тому, що трапиться, якщо невдалий запит знаходиться посередині транзакції).

Прикладом конфігурації для цього шляху є:

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

Клас повинен починатися більш-менш так:

namespace Your\DBAL;

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

Дуже дратує те, що ви повинні замінити кожен спосіб підключення, надаючи обгортку для обробки винятків. Використання затворів може полегшити біль там.


71

Моє рішення.

Перш ніж щось робити, перевірте:

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

Усі об’єкти будуть збережені. Але це зручно для певного класу чи деяких випадків. Якщо у вас є деякі служби із введеним менеджером сутностей, вони все одно будуть закриті.


це набагато краще, коли сам контейнер di недоступний. Дякую.
Hari KT

1
Ви також можете передати $ this-> entityManager-> getEventManager () у 3-му параметрі.
Медхат Гайєд

34

Symfony 2.0 :

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

Symfony 2.1+ :

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

6
ПОПЕРЕДЖЕННЯ: resetEntityManager застаріло, оскільки Symfony 2.1. Використовуйте resetManagerзамість цього
Франческо Казула

Це також скидає одиницю роботи?
грип

@flu Враховуючи, що клас EntityManager керує класом UnitOfWork, я підозрюю, що він би. Однак я не тестував цього, тому не можу бути впевненим.
Райал

26

Ось як я розгадав доктрину "EntityManager закритий". проблема. В основному кожен раз, коли виникає виняток (тобто дублікат ключа) або ненадання даних для обов’язкового стовпця, Doctrine закриє Entity Manager. Якщо ви все одно бажаєте взаємодіяти з базою даних, вам слід скинути налаштування Entity Manger, викликавши resetManager()метод, згаданий JGrinon .

У моїй програмі я запускав декількох споживачів RabbitMQ, які всі робили одне і те ж: перевіряли, чи є суть в базі даних, якщо так, повертати її, якщо не створювати, а потім повертати. Протягом декількох мілісекунд між перевіркою, чи вже існувала ця сутність, і створенням її іншим споживачем випадково зробили те саме, і створили відсутню сутність, змусивши іншого споживача спричинити виняток з повторенням ключа ( умова раси ).

Це призвело до проблеми з дизайном програмного забезпечення. В основному те, що я намагався зробити, це створити всі сутності в одній транзакції. Це може здаватися природним для більшості, але, безумовно, було концептуально помилковим у моєму випадку. Розглянемо таку проблему: мені довелося зберігати футбольний об’єкт матчу, який мав ці залежності.

  • група (наприклад, група A, група B ...)
  • раунд (наприклад, півфінал ...)
  • місце проведення (тобто стадіон, на якому проводиться матч)
  • статус матчу (напр., тайм)
  • дві команди, що грають матч
  • сам матч

Тепер, чому створення місця проведення має відбуватися в тій же транзакції, що і матч? Можливо, що я щойно отримав нове місце, якого немає у моїй базі даних, тому я маю його створити спочатку. Але також може бути, що на цьому майданчику може проходити інший матч, тому інший споживач, можливо, спробує створити його також одночасно. Отже, що мені потрібно було зробити, це створити всі залежності спочатку в окремих транзакціях, переконавшись, що я скидаю менеджер сутності у виняткові дублікати ключів. Я б сказав, що всі сутності, що знаходяться поруч із збігом, можна визначити як "спільні", оскільки вони потенційно можуть бути частиною інших транзакцій інших споживачів. Щось, що там не "ділиться", це сам матч, який, швидше за все, не буде створений двома споживачами одночасно.

Все це також призвело до іншого питання. Якщо ви скидаєте Entity Manager, усі об’єкти, які ви отримали перед скиданням, є для Doctrine абсолютно новими. Тож Доктрина не намагатиметься запустити ОНОВЛЕННЯ на них, а ВСТАВИТИ ! Тому переконайтеся, що ви створюєте всі свої залежності в логічно коректних транзакціях, а потім отримуєте всі свої об’єкти з бази даних, перш ніж встановлювати їх для цільової сутності. Розглянемо наступний код як приклад:

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

Отже, я вважаю, що це слід робити.

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

Сподіваюся, це допоможе :)


Фантастичне пояснення. Я знайшов щось подібне і вважав, що було б непогано внести свій внесок у вашу відповідь. Дуже дякую.
Anjana Silva

17

Ви можете скинути свою ЕМ так

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

У Symfony 4.2+ ви повинні використовувати пакет:

composer require symfony/proxy-manager-bridge

в іншому випадку ви отримуєте виняток:

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

Чим ви можете скинути entityManager так:

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

У контролері.

Виняток закриває Entity Manager. Це створює проблеми для об'ємної вставки. Щоб продовжити, потрібно перевизначити його.

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


1

Наскільки це варто, я виявив, що ця проблема траплялася в команді пакетного імпорту через цикл try / catch, що ловить помилку SQL (with em->flush()), з якою я нічого не робив. У моєму випадку це було тому, що я намагався вставити запис із властивістю, що не має дозволу, що залишилася як null.

Зазвичай це може спричинити критичний виняток і зупинити команду або контролер, але я просто реєстрував цю проблему і продовжував. Помилка SQL призвела до закриття менеджера сутності.

Перевірте свій dev.logфайл на наявність таких дурних помилок SQL, як ця, оскільки це може бути вашою виною. :)


1

З тією ж проблемою я зіткнувся під час тестування змін у Symfony 4.3.2

Я знизив рівень журналу до INFO

І знову провів тест

І журнал показав це:

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

Це означає, що якась помилка в коді спричиняє:

Doctrine\ORM\ORMException: The EntityManager is closed.

Тож бажано перевірити журнал


Не могли б ви надати додаткову інформацію про те, як перше пов’язане з другим?
Джордж Новік

1

Symfony v4.1.6

Доктрина v2.9.0

Процес вставки дублікатів у сховище

  1. Отримайте доступ до реєстру у своєму репозитарії


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

  1. Оберніть ризикований код у транзакції та скиньте менеджер на випадок


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

У мене було це питання. Це як я це виправив.

Здається, зв’язок переривається під час спроби змити або продовжити. Спроба знову відкрити це поганий вибір, оскільки створює нові проблеми. Я спробував зрозуміти, чому з'єднання було закрито, і виявив, що я робив занадто багато модифікацій, перш ніж продовжувати.

persist () раніше вирішив проблему.


0

Спробуйте використати:

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

перед початком транзакції.

За Connection::rollbackметодом він перевіряє наявність nestTransactionsWithSavepointsвластивості.


3
Чи можете ви розширити це?
paul.ago

0

Це справді давня проблема, але я просто мав подібну проблему. Я робив щось подібне:

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

Проблема полягала в тому, що було чітко від’єднано всі сутності, включаючи перше, та виведено помилку EntityManager закрито.

У моєму випадку рішення полягало в тому, щоб просто чітко визначити тип юридичної особи та залишити $entityOneвсе ще під EM:

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

0

Та сама проблема, вирішена за допомогою простого рефакторингу коду. Проблема іноді присутня, коли обов’язкове поле є нульовим, перш ніж виконувати анітування, спробуйте рефакторингувати ваш код. Кращий робочий процес може вирішити проблему.


-1

У мене була та ж помилка при використанні Symfony 5 / Doctrine 2. Одне з моїх полів було названо за допомогою зарезервованого слова MySQL "порядок", що спричинило DBALException. Коли ви хочете використовувати зарезервоване слово, вам потрібно уникнути його назви, використовуючи зворотні позначки. У формі анотації:

@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

Я зіткнувся з тією ж проблемою. Ознайомившись із кількома місцями, ось як я з цим справлявся.

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

Сподіваюся, це комусь допомагає!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.