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 2014

2
@JaySheth 엔티티 관리자는 DBAL 예외 후 또는 플러시 전에 EntityManager-> clear ()를 수행하는 경우 닫힐 수 있습니다. DBAL 예외를 사용하여 실행 흐름을 분기 한 다음 EntityManager 종료 오류로 끝나는 사람들을 보았습니다. 이 오류가 발생하면 프로그램의 실행 흐름에 문제가있는 것입니다.
ILikeTacos 2014 년

5
@AlanChavez-Doctrine을 사용하여 여러 스레드에서 동시에 액세스하는 테이블에 세마포어 플래그를 작성하기 때문에이 오류가 발생합니다. MySQL은 세마포어 생성을 시도하는 두 개의 경쟁 스레드 중 하나에 오류가 발생합니다. 키 제약 조건은 그중 하나만 성공할 수 있음을 의미하기 때문입니다. IMO에는 예상되는 MySQL 오류 를 안전하게 처리 수없는 Doctrine의 결함이 있습니다. 하나의 INSERT 문에 충돌이 발생하여 전체 MySQL 연결을 끊어야하는 이유는 무엇입니까?
StampyCode 2014

2
에서 데이터베이스에 예외를 기록하려고 app.exception_listener하지만 예외 (예 : 제약 조건 위반)가 연결을 닫은 경우에도이 오류가 표시됩니다.
Lg102

답변:


24

이것은 적어도 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 메서드를 재정의해야한다는 것입니다. 클로저를 사용하면 통증을 완화 할 수 있습니다.


71

내 솔루션.

무엇이든 확인하기 전에 :

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

6
경고 : resetEntityManager Symfony 2.1부터는 더 이상 사용되지 않습니다. 사용 resetManager대신에
프란체스코 Casula

작업 단위도 재설정됩니까?
독감

@flu EntityManager 클래스가 UnitOfWork 클래스를 관리한다는 점을 고려하면 그럴 것이라고 생각합니다. 그러나 나는 이것을 테스트하지 않았기 때문에 확신 할 수 없습니다.
Ryall

26

이것이 제가 "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();

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


환상적인 설명. 나는 비슷한 것을 발견했고 당신의 대답에 기여하면 좋을 것이라고 생각했습니다. 대단히 감사합니다.
Anjana Silva

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

컨트롤러에서.

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

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

em->flush()내가 아무것도하지 않은 SQL 오류를 잡는 try / catch 루프 때문에 일괄 가져 오기 명령 에서이 문제가 발생 한다는 것을 알았습니다. 제 경우에는 null이 아닌 속성이 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.

따라서 로그를 확인하는 것이 좋습니다.


첫 번째가 두 번째와 어떤 관련이 있는지에 대한 추가 정보를 제공 할 수 있습니까?
George Novik 19 년

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

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

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

0

간단한 코드 리팩토링으로 동일한 문제가 해결되었습니다. 문제는 필수 필드가 null 인 경우 가끔 존재합니다. anithing을 수행하기 전에 코드를 리팩터링하십시오. 더 나은 워크 플로우가 문제를 해결할 수 있습니다.


-1

Symfony 5 / Doctrine 2를 사용하여 동일한 오류가 발생했습니다. 내 필드 중 하나가 MySQL 예약어 "order"를 사용하여 이름이 지정되어 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();
}

이것이 누군가를 돕기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.