EntityManager закрыт

87
[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();

Кто-нибудь знает, как переподключить?

Ули
источник
Почему закрывается менеджер сущности?
Джей Шет
2
@JaySheth Диспетчер сущностей может закрыться после исключения DBAL или если вы выполняете EntityManager-> clear () перед сбросом. Я видел, как некоторые люди использовали исключения DBAL для ветвления потока выполнения, а затем получали ошибку закрытия EntityManager. Если вы получаете эту ошибку, в вашей программе что-то не так.
ILikeTacos
5
@AlanChavez - Я получаю эту ошибку, потому что я использую Doctrine для записи флага семафора в таблицу, к которой одновременно обращаются несколько потоков. MySQL выдаст ошибку в одном из двух конкурирующих потоков, пытающихся создать семафор, потому что ограничение ключа означает, что только один из них может быть успешным. IMO есть недостаток в Doctrine, который не позволяет безопасно обрабатывать ожидаемые ошибки MySQL. Почему все соединение MySQL должно быть отключено из-за конфликта одного оператора INSERT?
StampyCode
2
Вы также увидите эту ошибку, если пытаетесь регистрировать исключения в базе данных, app.exception_listenerно исключение (например, нарушение ограничения) закрыло соединение.
Lg102

Ответы:

25

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

Единственный способ, который я нашел для решения этой проблемы, - это создать свой собственный класс DBAL Connection, обернуть Doctrine один и обеспечить обработку исключений (например, несколько раз повторить попытку перед тем, как передать исключение в 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 {
  // ...
}

Очень раздражает то, что вам нужно переопределить каждый метод Connection, предоставляющий вашу оболочку обработки исключений. Использование закрытия может облегчить боль.

Альдо Стракуаданио
источник
74

Мое решение.

Прежде чем что-либо делать, проверьте:

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

Все сущности будут сохранены. Но это удобно для определенного класса или некоторых случаев. Если у вас есть сервисы с внедренным entitymanager, он все равно будет закрыт.

Грегспэрроу
источник
это намного лучше, когда сам контейнер di недоступен. Спасибо.
Hari KT
1
вы также можете передать $ this-> entityManager-> getEventManager () в третьем параметре.
Medhat Gayed
34

Symfony 2.0 :

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

Symfony 2.1+ :

$em = $this->getDoctrine()->resetManager();
Luisbg
источник
6
ВНИМАНИЕ: resetEntityManager устарело, начиная с Symfony 2.1. Используйте resetManagerвместо
Francesco Casula
Это также сбрасывает Единицу Работы?
грипп
@flu Учитывая, что класс EntityManager управляет классом UnitOfWork, я подозреваю, что это так. Однако я не проверял это, поэтому не могу быть уверен.
Ryall
28

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

В моем приложении я запускал несколько потребителей RabbitMQ, которые все делали одно и то же: проверяли, есть ли объект в базе данных, если да, верните его, если не создайте, а затем верните его. За несколько миллисекунд между проверкой существования этой сущности и ее созданием другой потребитель сделал то же самое и создал недостающую сущность, в результате чего другой потребитель столкнулся с дублирующимся ключевым исключением ( состояние гонки ).

Это привело к проблеме разработки программного обеспечения. По сути, я пытался создать все сущности в одной транзакции. Для большинства это может показаться естественным, но в моем случае это было определенно концептуально неверно. Рассмотрим следующую проблему: мне нужно было сохранить сущность футбольного матча, у которой были эти зависимости.

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

Итак, почему создание места проведения должно происходить в той же транзакции, что и матч? Возможно, я только что получил новое место, которого нет в моей базе данных, поэтому я должен сначала его создать. Но также может быть, что это место может провести еще один матч, поэтому другой потребитель, вероятно, попытается создать его в то же время. Поэтому мне нужно было сначала создать все зависимости в отдельных транзакциях, убедившись, что я сбрасываю диспетчер сущностей в исключении с дублированным ключом. Я бы сказал, что все сущности рядом с совпадением могут быть определены как «общие», потому что они потенциально могут быть частью других транзакций у других потребителей. То, что не является «общим», - это сам матч, который вряд ли будет создан двумя потребителями одновременно.

Все это также привело к другой проблеме. Если вы сбросите Entity Manager, все объекты, которые вы получили перед сбросом, будут совершенно новыми для Doctrine. Таким образом, 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();

Я надеюсь, что это помогает :)

Франческо Казула
источник
Фантастическое объяснение. Я нашел нечто похожее и подумал, что было бы неплохо внести свой вклад в ваш ответ. Большое спасибо.
Анджана Сильва
17

Вы можете сбросить свой EM так

// 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++;
    }               
  }                      
}
Вадим
источник
2

Как бы то ни было, я обнаружил, что эта проблема возникает в команде пакетного импорта из-за того, что цикл try / catch улавливает ошибку SQL (с 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 () ранее решал проблему.

user3046563
источник
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 закрыто.

В моем случае решение заключалось в том, чтобы просто очистить отдельный тип Entity и оставить $entityOneвсе еще под EM:

$this->em->clear(SomeEntityClass::class);
Никола Лончар
источник
Вызов Doctrine \ ORM \ EntityManager :: clear () с любыми аргументами для очистки определенных сущностей устарел и не будет поддерживаться в Doctrine ORM 3.0
Foued MOUSSI
0

Та же проблема, решенная простым рефакторингом кода. Проблема иногда возникает, когда обязательное поле имеет значение null, прежде чем выполнять что-либо, попробуйте реорганизовать код. Улучшенный рабочий процесс может решить проблему.

Аксель Брише
источник
-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();
}

Надеюсь, это кому-то поможет!

Mayank Tiwari
источник