EntityManager因异常关闭重启后,如何正确更新持久化实体?
解决方案
核心问题分析
问题根源是不同EntityManager实例之间共享实体对象:旧EntityManager创建/查询的ImportLock和关联的User实体,在新EntityManager重置后处于游离状态,新EM无法识别这些实体的管理状态,进而触发各类持久化异常。
具体修复步骤
1. 提前保存关键ID
在初始设置锁激活时,先将锁和用户的ID保存到变量,避免旧实体失效后无法查询:
$lockRepo = $this->entityManager->getRepository(ImportLock::class); $lock = $lockRepo->findOneByUser($user); if (!$lock) { $lock = new ImportLock(); $lock->setUser($user); // 确保关联用户 } if ($lock->isActive()) { return; } $lock->setActive(); $this->entityManager->persist($lock); $this->entityManager->flush(); // 提前保存ID,后续重置EM后用这些ID查询 $lockId = $lock->getId(); $userId = $user->getId();
2. 在finally块中使用当前有效EM并重新获取实体
不管EM是否被重置,在finally块中都从当前有效的EM重新查询ImportLock(同时确保关联的User也被当前EM管理):
// 执行导入逻辑 try { $this->entityManager->getConnection()->beginTransaction(); doImport(); $this->entityManager->getConnection()->commit(); } catch (\Exception $e) { $this->entityManager->getConnection()->rollback(); if (!$this->entityManager->isOpen()) { $this->doctrineRegistry->resetManager(); } } finally { // 获取当前有效的EntityManager $currentEm = $this->doctrineRegistry->getManager(); // 重新查询锁,用fetch join加载关联的User,确保两者都被当前EM管理 $lock = $currentEm->createQueryBuilder() ->select('lock', 'user') ->from(ImportLock::class, 'lock') ->join('lock.user', 'user') ->where('lock.id = :lockId') ->andWhere('user.id = :userId') ->setParameters([ 'lockId' => $lockId, 'userId' => $userId ]) ->getQuery() ->getOneOrNullResult(); if ($lock) { $lock->setInactive(); $currentEm->persist($lock); $currentEm->flush(); } }
3. 可选:优化关联映射配置
如果希望降低关联实体的管理风险,可以在ImportLock的user关联上添加cascade={"persist"},但这只是辅助手段,核心仍需保证实体由当前EM管理:
// ImportLock实体类中的关联配置 /** * @ORM\ManyToOne(targetEntity=User::class, cascade={"persist"}) * @ORM\JoinColumn(nullable=false) */ private $user;
修复逻辑说明
- 始终使用当前有效的EntityManager操作实体,彻底规避已关闭EM的问题。
- 重新查询实体(含关联的User),确保所有操作的实体都处于当前EM的管理状态,消除游离实体引发的异常。
- 使用fetch join提前加载关联的User,避免懒加载时调用旧EM实体导致的错误。
内容的提问来源于stack exchange,提问作者Andrei Herford
相关产品推荐
相关产品推荐

