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

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


エンティティマネージャが閉じるのはなぜですか?
ジェイシェス2014

2
@JayShethエンティティマネージャは、DBAL例外の後、またはフラッシュの前にEntityManager-> clear()を実行している場合に閉じる可能性があります。DBAL例外を使用して実行フローを分岐した後、EntityManagerのクローズエラーが発生する人がいます。このエラーが発生した場合は、プログラムの実行フローに問題があります。
ILikeTacos 2014年

5
@ AlanChavez-Doctrineを使用して、複数のスレッドから同時にアクセスされているテーブルにセマフォフラグを書き込んでいるため、このエラーが発生します。キー制約はそのうちの1つだけが成功できることを意味するため、MySQLはセマフォを作成しようとしている2つの競合するスレッドの1つにエラーを出します。IMO Doctrineには、予想されるMySQLエラーを安全に処理できないという欠陥があります。1つのINSERTステートメントに競合があるため、MySQL接続全体を切断する必要があるのはなぜですか?
StampyCode 2014年

2
のデータベースに例外を記録しようとしているapp.exception_listenerが、例外(制約違反など)が接続を閉じた場合にも、このエラーが表示されます。
lg102 2016年

回答:


25

少なくともSymfony2.0とDoctrine2.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の各メソッドをオーバーライドする必要があることです。クロージャーを使用すると、そこでの痛みを和らげることができます。


74

私の解決策。

何かをする前にチェックしてください:

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

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


これは、diコンテナ自体が利用できない場合にはるかに優れています。ありがとうございました。
ハリKT

1
3番目のパラメーターで$ this-> entityManager-> getEventManager()を渡すこともできます。
Medhat Gayed 2017年

34

Symfony 2.0

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

symfony 2.1+

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

6
警告: resetEntityManager Symfony2.1以降は非推奨です。resetManager代わりに使用
Francesco Casula 2015年

これにより、作業単位もリセットされますか?
インフルエンザ

@flu EntityManagerクラスがUnitOfWorkクラスを管理していることを考えると、そうなると思います。しかし、私はこれをテストしていないので、確信が持てません。
Ryall 2016年

28

これが私が「EntityManagerが閉じられている」という教義を解決した方法です。問題。基本的に、例外(つまり、重複キー)があるか、必須列にデータを提供しないたびに、Doctrineはエンティティマネージャーを閉じます。それでもデータベースを操作したい場合はresetManager()JGrinonが説明したメソッドを呼び出して、エンティティマネージャーをリセットする必要があります。

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

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

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

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

これらすべてが別の問題にもつながりました。エンティティマネージャーをリセットすると、リセットする前に取得したすべてのオブジェクトは、まったく新しいDoctrine用です。したがって、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();

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


素晴らしい説明。似たようなものを見つけて、あなたの答えに貢献できたらいいなと思いました。どうもありがとうございました。
AnjanaSilva19年

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++;
    }               
  }                      
}

2

em->flush()私が何もしなかったSQLエラー(with )をキャッチする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.

したがって、ログを確認することをお勧めします


最初のものが2番目のものとどのように関連しているかについての追加情報を提供できますか?
GeorgeNovik19年

1

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

  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

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

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

私の場合の解決策は、異なるタイプのエンティティを明確にし$entityOne、EMの下に置いたままにすることでした。

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

特定のエンティティをクリアするための引数を指定してDoctrine \ ORM \ EntityManager :: clear()を呼び出すことは非推奨であり、Doctrine ORM 3.0
FouedMOUSSI20年

0

同じ問題が、単純なコードリファクタリングで解決されました。必須フィールドがnullの場合、問題が発生することがあります。これを行う前に、コードをリファクタリングしてみてください。より良いワークフローで問題を解決できます。


-1

Symfony 5 / Doctrine 2を使用しても同じエラーが発生しました。フィールドの1つに、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.