So löste ich die Doktrin "Der EntityManager ist geschlossen." Problem. Grundsätzlich führt Doctrine jedes Mal, wenn eine Ausnahme vorliegt (dh ein doppelter Schlüssel) oder wenn keine Daten für eine obligatorische Spalte angegeben werden, dazu, dass Doctrine den Entity Manager schließt. Wenn Sie dennoch mit der Datenbank interagieren möchten, müssen Sie den Entity Manager zurücksetzen, indem Sie die resetManager()
von JGrinon erwähnte Methode aufrufen .
In meiner Anwendung habe ich mehrere RabbitMQ-Konsumenten ausgeführt, die alle dasselbe taten: Überprüfen, ob eine Entität in der Datenbank vorhanden war, wenn ja, geben Sie sie zurück, wenn nicht, erstellen Sie sie und geben Sie sie dann zurück. In den wenigen Millisekunden zwischen der Überprüfung, ob diese Entität bereits vorhanden war, und ihrer Erstellung hat ein anderer Verbraucher dasselbe getan und die fehlende Entität erstellt, sodass der andere Verbraucher eine doppelte Schlüsselausnahme ( Race-Bedingung ) aufweist.
Dies führte zu einem Software-Design-Problem. Grundsätzlich habe ich versucht, alle Entitäten in einer Transaktion zu erstellen. Dies mag sich für die meisten natürlich anfühlen, war aber in meinem Fall definitiv konzeptionell falsch. Betrachten Sie das folgende Problem: Ich musste eine Fußballspiel-Entität speichern, die diese Abhängigkeiten hatte.
- eine Gruppe (zB Gruppe A, Gruppe B ...)
- eine Runde (zB Halbfinale ...)
- ein Veranstaltungsort (dh ein Stadion, in dem das Spiel stattfindet)
- einen Spielstatus (zB Halbzeit, Vollzeit)
- die beiden Mannschaften spielen das Spiel
- das Spiel selbst
Warum sollte die Erstellung des Veranstaltungsortes in derselben Transaktion wie das Spiel erfolgen? Es kann sein, dass ich gerade einen neuen Veranstaltungsort erhalten habe, der nicht in meiner Datenbank enthalten ist, sodass ich ihn zuerst erstellen muss. Es kann aber auch sein, dass an diesem Veranstaltungsort ein weiteres Spiel stattfindet, sodass ein anderer Verbraucher wahrscheinlich gleichzeitig versucht, es zu erstellen. Ich musste also zuerst alle Abhängigkeiten in separaten Transaktionen erstellen, um sicherzustellen, dass ich den Entitätsmanager in einer doppelten Schlüsselausnahme zurücksetzte. Ich würde sagen, dass alle Entitäten dort neben dem Match als "gemeinsam genutzt" definiert werden könnten, da sie möglicherweise Teil anderer Transaktionen bei anderen Verbrauchern sein könnten. Etwas, das dort nicht "geteilt" wird, ist das Match selbst, das wahrscheinlich nicht von zwei Verbrauchern gleichzeitig erstellt wird.
All dies führte auch zu einem anderen Problem. Wenn Sie den Entity Manager zurücksetzen, sind alle Objekte, die Sie vor dem Zurücksetzen abgerufen haben, für Doctrine völlig neu. Doctrine wird also nicht versuchen, ein UPDATE auf ihnen auszuführen , sondern ein INSERT ! Stellen Sie daher sicher, dass Sie alle Ihre Abhängigkeiten in logisch korrekten Transaktionen erstellen und dann alle Ihre Objekte aus der Datenbank zurückrufen, bevor Sie sie auf die Zielentität setzen. Betrachten Sie den folgenden Code als Beispiel:
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
So denke ich, sollte es gemacht werden.
$group = $this->createGroupIfDoesNotExist($groupData);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);
$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);
$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();
Ich hoffe, es hilft :)