这是我解决Doctrine "EntityManager已关闭"问题的方法。
基本上每次出现异常(即重复键)或者未提供必填列的数据,Doctrine就会关闭EntityManager。如果你仍想与数据库交互,你需要调用resetManager()方法来重置Entity Manger,正如JGrinon所述。
在我的应用程序中,我运行了多个RabbitMQ消费者,它们都在做同样的事情:检查数据库中是否存在某个实体,如果存在则返回该实体,否则创建它然后返回它。
在检查实体是否存在并进行创建之间的几毫秒内,另一个消费者也在做同样的事情,并创建了缺失的实体,导致另一个消费者遭遇了重复键异常(竞态条件)。
这导致了软件设计问题。基本上,我试图在一个事务中创建所有实体。对大多数人来说,这可能感觉很自然,但在我的情况下确实在概念上是错误的。考虑以下问题:我必须存储一个足球比赛实体,其中包含以下依赖项:
• 一个分组(例如A组,B组...)
• 一个轮次(例如半决赛...)
• 一个场馆(即比赛举行的体育场)
• 一个比赛状态(例如中场,全场)
• 两个参加比赛的队伍
• 比赛本身
现在,为什么场馆创建应该与比赛在同一事务中进行?可能我刚刚收到了一个新场馆,但它并不在我的数据库中,所以我必须先创建它。但它也可能会举办其他比赛,因此另一个消费者可能会尝试同时创建它。所以我必须首先在单独的事务中创建所有依赖项,确保在重复键异常时重置实体管理器。我认为除了比赛之外,在其中的所有实体都可以被定义为“共享”,因为它们可能成为其他消费者中的其他事务的一部分。在其中没有“共享”的是比赛本身,它不太可能被两个消费者同时创建。因此,在最后一个事务中,我希望只看到比赛和两个队伍之间的关系。
所有这些也导致了另一个问题。如果你重置了Entity Manager,你之前检索到的所有对象对于Doctrine来说都是全新的。所以Doctrine不会尝试在它们上运行UPDATE,而是INSERT!因此,请确保在逻辑上正确的事务中创建所有依赖关系,然后从数据库中检索所有对象,再将它们设置为目标实体。请考虑以下代码作为示例:
$group = $this->createGroupIfDoesNotExist($groupData);
$match->setGroup($group);
$venue = $this->createVenueIfDoesNotExist($venueData);
$round = $this->createRoundIfDoesNotExist($roundData);
这就是我认为应该这样做的方式。
$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();
希望这能有所帮助 :)
app.exception_listener
中的数据库,但异常(例如约束违规)关闭了连接,则还会看到此错误。 - Lg102