web-dev-qa-db-ja.com

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

誰が再接続する方法を考えていますか?

71
Ueli

これは、少なくともSymfony 2.0およびDoctrine 2.1の場合、EntityManagerを閉じた後に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の各メソッドをオーバーライドする必要があることです。クロージャーを使用すると、そこの痛みを緩和できます。

24

私の解決策。

何かを行う前に確認してください:

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

すべてのエンティティが保存されます。しかし、特定のクラスや場合によっては便利です。 entitymanagerが挿入されたサービスがある場合、まだ閉じられています。

56
Gregsparrow

Symfony 2.

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

Symfony 2.1 +

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

これが、私がDoctrine "EntityManagerが閉じています。"の問題を解決した方法です。基本的に、例外が発生するたびに(つまり、キーの複製)DoctrineはEntity Managerを閉じます。それでもデータベースとやり取りしたい場合は、JGrinonで述べたようにresetManager()メソッドを呼び出してEntity Mangerをリセットする必要があります。

私のアプリケーションでは、すべて同じことを行っている複数のRabbitMQコンシューマーを実行していました。データベースにエンティティが存在するかどうかを確認し、存在する場合はそれを返し、そうでない場合は作成してから返します。そのエンティティが既に存在するかどうかを確認してから作成するまでの数ミリ秒で、別のコンシューマーが同じことを行い、欠落しているエンティティを作成して、他のコンシューマーに重複キー例外が発生しました(競合状態)。

これにより、ソフトウェア設計の問題が発生しました。基本的に、私がやろうとしていたことは、1つのトランザクションですべてのエンティティを作成することでした。これはほとんどの人にとって自然に感じるかもしれませんが、私の場合は間違いなく概念的に間違っていました。次の問題を考慮してください。これらの依存関係を持つフットボールの試合エンティティを保存する必要がありました。

  • グループ(例:グループA、グループB ...)
  • ラウンド(例:準決勝...)
  • 会場(つまり、試合が行われているスタジアム)
  • 試合のステータス(例:ハーフタイム、フルタイム)
  • 試合をしている2つのチーム
  • 試合自体

では、会場の作成を試合と同じトランザクションで行う必要があるのはなぜですか?データベースにない新しい会場を受け取ったばかりなので、最初に作成する必要があります。しかし、その会場が別の試合を主催する可能性もあるため、別の消費者が同時にそれを作成しようとする可能性があります。そのため、最初に個別のトランザクションですべての依存関係を作成し、重複キー例外でエンティティマネージャーをリセットする必要がありました。一致の横にあるすべてのエンティティは、潜在的に他のコンシューマーの他のトランザクションの一部である可能性があるため、「共有」と定義できます。そこに「共有」されていないものは、2人の消費者によって同時に作成される可能性が低いマッチ自体です。したがって、最後のトランザクションでは、試合と、2つのチームと試合の間の関係のみが表示されると予想しています。

これはすべて別の問題にもつながりました。 エンティティマネージャーをリセットすると、リセットする前に取得したすべてのオブジェクトがDoctrine_まったく新しいものになります。そのため、Doctrineはを更新しますが、INSERT make論理的に正しいトランザクションですべての依存関係を作成してから、データベースからすべてのオブジェクトを取得してから、ターゲットエンティティに設定してください。例として次のコードを検討してください。

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

私はそれが役立つことを願っています:)

18

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

コントローラー内。

例外はエンティティマネージャを閉じます。これにより、一括挿入で問題が発生します。続行するには、再定義する必要があります。

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

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

価値があるのは、何もしなかったSQLエラー(em->flush())をキャッチするtry/catchループが原因で、この問題がバッチインポートコマンドで発生していることです。私の場合、それは、nullとして許可されていないプロパティを持つレコードを挿入しようとしたためでした。

通常、これにより重大な例外が発生し、コマンドまたはコントローラーが停止しますが、代わりにこの問題を記録して続行しました。 SQLエラーにより、エンティティマネージャが閉じられました。

dev.logファイルをチェックして、このような愚かなSQLエラーがないかどうかを確認してください。 :)

1
Adambean

これは本当に古い問題ですが、同様の問題がありました。私はこのようなことをしていました:

// 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は閉じられています。

私の場合、solutionは、明確なタイプのエンティティを明確にし、$entityOneをEMのままにしておくことでした。

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

Symfony v4.1.6

Doctrine v2.9.0

リポジトリに重複を挿入するプロセス

  1. リポジトリのレジストリにアクセスします


//begin of repo

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

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

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

0

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.

そのため、ログを確認することをお勧めします

0
Babak Bandpey

使用してみてください:

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

トランザクションを開始する前。

Connection::rollbackメソッドでは、 nestTransactionsWithSavepoints プロパティをチェックします。

0
zechim

この問題がありました。これは私がそれを修正した方法です。

フラッシュまたは永続化を試行中に接続が閉じているようです。新しい問題を作成するため、再開しようとするのは悪い選択です。接続が閉じられた理由を理解しようとしましたが、永続化する前にあまりにも多くの変更を行っていました。

persist()は以前に問題を解決しました。

0
user3046563

この問題に関する興味深い記事を見つけました

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

Doctrine 2例外EntityManagerが閉じています

0
stephan.mada