这就是我解决“EntityManager 已关闭”的教义的方法。问题。基本上每次出现异常(即重复键)或不为必填列提供数据时,都会导致 Doctrine 关闭实体管理器。如果您仍想与数据库交互,则必须通过调用JGrinonresetManager()
提到的方法来重置实体管理器。
在我的应用程序中,我运行了多个 RabbitMQ 消费者,它们都在做同样的事情:检查数据库中是否存在实体,如果是则返回它,如果没有则创建它然后返回它。在检查该实体是否已经存在和创建它之间的几毫秒内,另一个消费者碰巧做了同样的事情并创建了丢失的实体,使另一个消费者产生了重复键异常(竞争条件)。
这导致了软件设计问题。基本上我想做的是在一个事务中创建所有实体。这对大多数人来说可能感觉很自然,但在我的情况下,这在概念上肯定是错误的。考虑以下问题:我必须存储一个具有这些依赖关系的足球比赛实体。
- 一个组(例如 A 组、B 组...)
- 一轮(例如半决赛......)
- 场地(即比赛进行的体育场)
- 比赛状态(例如半场、全场)
- 参加比赛的两支球队
- 比赛本身
现在,为什么场地创建应该与比赛在同一个交易中?可能是我刚刚收到了一个不在我的数据库中的新场地,所以我必须先创建它。但也可能是该场地可能举办另一场比赛,因此另一位消费者可能会同时尝试创建它。所以我必须做的是首先在单独的事务中创建所有依赖项,确保我在重复键异常中重置实体管理器。我想说匹配项旁边的所有实体都可以定义为“共享”,因为它们可能是其他消费者的其他交易的一部分。那里没有“共享”的东西是匹配本身不太可能由两个消费者同时创建。
所有这些也导致了另一个问题。如果您重置实体管理器,那么您在重置之前检索到的所有对象对于 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();
我希望它有帮助:)