developer tip

EntityManager가 닫힙니다.

copycodes 2020. 10. 26. 08:07
반응형

EntityManager가 닫힙니다.


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

다시 연결하는 방법에 대한 아이디어가 있습니까?


이것은 적어도 Symfony 2.0과 Doctrine 2.1의 경우 EntityManager가 닫힌 후 다시 열 수 없기 때문에 매우 까다로운 문제입니다.

이 문제를 극복하기 위해 내가 찾은 유일한 방법은 자신의 DBAL 연결 클래스를 만들고 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 메서드를 재정의해야한다는 것입니다. 클로저를 사용하면 통증을 완화 할 수 있습니다.


내 솔루션.

무엇이든 확인하기 전에 :

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

모든 엔티티가 저장됩니다. 그러나 특정 클래스 또는 일부 경우에 편리합니다. 삽입 된 entitymanager가있는 일부 서비스가있는 경우 여전히 닫힙니다.


Symfony 2.0 :

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

Symfony 2.1 이상 :

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

이것이 제가 "The EntityManager is closed"라는 교리를 해결 한 방법 입니다. 발행물. 기본적으로 예외 (예 : 중복 키)가있을 때마다 Doctrine은 엔티티 관리자를 닫습니다. 여전히 데이터베이스와 상호 작용하려면 JGrinon에서resetManager() 언급 한 메서드를 호출하여 Entity Manger를 재설정해야합니다 .

내 응용 프로그램에서 나는 모두 같은 일을하고있는 여러 RabbitMQ 소비자를 실행하고있었습니다. 엔티티가 데이터베이스에 있는지 확인하고 있다면 반환하고 생성하지 않으면 반환 한 다음 반환합니다. 해당 엔티티가 이미 존재하는지 확인하고 생성하는 사이 몇 밀리 초 동안 다른 소비자가 동일한 작업을 수행하고 누락 된 엔티티를 생성하여 다른 소비자에게 중복 키 예외 ( 경쟁 조건 )를 발생시킵니다.

이로 인해 소프트웨어 설계 문제가 발생했습니다. 기본적으로 내가하려는 것은 하나의 트랜잭션에서 모든 엔티티를 만드는 것입니다. 이것은 대부분의 경우 자연 스럽지만 제 경우에는 확실히 개념적으로 잘못되었습니다. 다음 문제를 고려하십시오. 이러한 종속성이있는 football Match 엔티티를 저장해야했습니다.

  • 그룹 (예 : 그룹 A, 그룹 B ...)
  • 라운드 (예 : 준결승전 ...)
  • 장소 (예 : 경기가 열리는 경기장)
  • 경기 상태 (예 : 전반전, 풀 타임)
  • 경기를 두 팀
  • 경기 자체

이제 장소 생성이 경기와 동일한 트랜잭션에 있어야하는 이유는 무엇입니까? 데이터베이스에없는 새 장소를 방금 받았을 수 있으므로 먼저 만들어야합니다. 그러나 해당 장소에서 다른 경기를 주최 할 수 있으므로 다른 소비자도 동시에 제작을 시도 할 수 있습니다. 그래서 내가해야 할 일은 중복 키 예외에서 엔티티 관리자를 재설정하도록 별도의 트랜잭션에서 먼저 모든 종속성을 만드는 것입니다. 일치 항목 옆에있는 모든 항목은 잠재적으로 다른 소비자의 다른 트랜잭션의 일부가 될 수 있기 때문에 "공유"로 정의 될 수 있습니다. "공유"되지 않는 것은 동시에 두 소비자가 만들지 않을 일치 자체가 있습니다.

이 모든 것이 또 다른 문제로 이어졌습니다. 엔티티 관리자를 재설정하면 재설정하기 전에 검색 한 모든 객체는 완전히 새로운 Doctrine 용입니다. 그래서 교리는 실행하려고하지 않습니다 UPDATE를 그들 그러나에 INSERT ! 따라서 논리적으로 올바른 트랜잭션에서 모든 종속성을 만든 다음 대상 엔터티로 설정하기 전에 데이터베이스에서 모든 개체를 다시 검색해야합니다. 다음 코드를 예로 고려하십시오.

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

나는 그것이 도움이되기를 바랍니다 :)


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

컨트롤러에서.

예외는 엔티티 관리자를 닫습니다. 이것은 대량 삽입에 문제를 일으 킵니다. 계속하려면 재정의해야합니다.

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

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

em->flush()내가 아무것도하지 않은 SQL 오류를 포착하는 try / catch 루프 때문에 일괄 가져 오기 명령 에서이 문제가 발생 한다는 것을 알았습니다. 제 경우에는 null이 아닌 속성이 null로 남아있는 레코드를 삽입하려고했기 때문입니다.

일반적으로 이로 인해 심각한 예외가 발생하고 명령 또는 컨트롤러가 중지되지만 대신이 문제를 로깅하고 계속했습니다. SQL 오류로 인해 엔티티 관리자가 닫혔습니다.

dev.log귀하의 잘못 일 수 있으므로 이와 같은 어리석은 SQL 오류가 있는지 파일을 확인하십시오 . :)


나는이 문제가 있었다. 이것은 내가 그것을 고친 방법입니다.

플러시 또는 지속을 시도하는 동안 연결이 닫히는 것 같습니다. 다시 열려고하면 새로운 문제가 발생하므로 잘못된 선택입니다. 나는 연결이 왜 닫혔는지 이해하려고 노력했고 지속되기 전에 너무 많은 수정을하고 있음을 발견했습니다.

persist ()는 이전에 문제를 해결했습니다.


다음을 사용해보십시오.

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

거래를 시작하기 전에.

Connection::rollback있어서 그것으로 확인 nestTransactionsWithSavepoints속성.


이것은 정말 오래된 문제이지만 비슷한 문제가 있습니다. 나는 다음과 같이하고 있었다.

// 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가 닫힙니다.

제 경우 솔루션 은 고유 한 유형의 엔티티를 지우고 $entityOneEM 아래에 그대로 두는 것입니다 .

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

Symfony v4.1.6

교리 v2.9.0

저장소에 중복 항목 삽입 프로세스

  1. Get access a registry in your repo

//begin of repo

/** @var RegistryInterface */
protected $registry;

public function __construct(RegistryInterface $registry)
{
    $this->registry = $registry;
    parent::__construct($registry, YourEntity::class);
}

  1. Wrap risky code into transaction and rested manager in case of exception

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


I found an interesting article about this problem

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

Doctrine 2 Exception EntityManager is closed


I faced the same problem while testing the changes in Symfony 4.3.2

I lowered the log level to INFO

And ran the test again

And the logged showed this:

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

This means that some error in the code causes the:

Doctrine\ORM\ORMException: The EntityManager is closed.

So it is a good idea to check the log


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

I faced the same problem. After looking at several places here is how I dealt with it.

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

Hope this helps someone!

참고URL : https://stackoverflow.com/questions/14258591/the-entitymanager-is-closed

반응형