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


답변

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


답변

컨트롤러에서.

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

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