これが私が「EntityManagerが閉じられている」という教義を解決した方法です。問題。基本的に、例外がある(つまり、キーが重複している)たびに、または必須の列にデータを提供しないと、Doctrineはエンティティマネージャーを閉じます。それでもデータベースを操作したい場合は、 JGrinonresetManager()
で説明されているように、メソッドを呼び出してエンティティマネージャーをリセットする必要があります。
私のアプリケーションでは、すべて同じことを行っている複数の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();
お役に立てば幸いです:)