我正在开发一个Doctrine应用程序,我在两个实体之间有一对多的关系,我称之为Foo'并且' Bar'。 '酒吧'是这种关系的拥有方,在这种情况下可能看起来很奇怪。我需要一个接一个地处理属于foo的所有条形并刷新更改。但是,条形实体的大小非常大(每个几兆字节)。因为每个foo可以有很多条形,所以我的所有RAM都会耗尽一个简单的循环。我试图通过在处理和刷新每个条形图之后进行刷新和分离来解决这个问题,如下所示:
foreach ($foo->getBars() as $bar) {
$bar->setSomething(...potentially large blobs...);
// ... dozens of more setters (and getters)
$em->flush();
// or: $em->flush($bar);
$em->detach($bar);
}
// unrelated code
$foo->setDoStuff(...)
$em->flush();
然而,这在第二次迭代时失败,因为由于某种原因,第二次flush()将遇到对象图中的第一个条并抱怨它没有被持久化(它是非托管的)。错误消息是:"通过关系找到了一个新实体' Foo#bars'没有配置为级联实体的持久化操作"
如果我只刷新特定的$ bar而不是所有实体,则循环成功完成,但是以后再次使用$ em-> flush()而不再使用参数,因为它将会遇到那些不受管理的实体。
分离$ foo会解决这个问题,但遗憾的是这是不可接受的,因为代码中有许多其他对$ foo的引用,可能希望稍后修改并刷新它。
我该如何解决这个问题?与clear()/ detach()相关的文档中的所有示例似乎都非常简单,并且不涉及关系。
理想情况下,有一种方法可以将对象恢复为非水合代理延迟加载状态,就像$ em-> getReference()获得的那样。然后垃圾收集器将释放以前所有字段使用的内存。
答案 0 :(得分:0)
我设法为我的特定用例修复了这个问题,但似乎无法实现一个非常通用,美观,无处不在的解决方案。
要理解的重要一点是Doctrine将如何加载实体并构建一个彼此引用的对象的整个图形。为了正确分离任何实体,不得在任何地方对它进行任何引用。如果编程非常仔细,这是可能的,但一般来说,很难保证代码的某些不相关部分没有获得对要分离的实体的引用,或者更糟糕的是,没有将它附加到实体图。 Doctrine将在每次刷新操作时遍历此图,并查找任何引用的实体,甚至是已分离的实体。
上面代码中的实际问题是$foo->getBars()
初始化一个集合,该集合最终将包含对所有“Bar”实体的引用。在每次刷新时,Doctrine将在其身份图中遇到$foo
并且$foo
引用这个物化的“Bar”集合,后者又引用“Bar”实体本身。因此它会尝试再次冲洗它们。
我的解决方案是这样的:我完全避免使用集合并逐个获取“Bar”实体,以确保没有其他任何引用它们。这些方面的东西:
$sql = "SELECT b.id FROM Bar b WHERE b.foo = :foo";
$rows = $em->createQuery($sql)->setParameter('foo', $foo)->getScalarResult();
$repository = $this->em->getRepository('Bar');
foreach ($rows as $row) {
$bar = $repository->find($row['id']);
$bar->setSomething(...potentially large blobs...);
// ... dozens of more setters (and getters)
$em->flush($bar);
$em->detach($bar);
}
// unrelated code can now safely flush, because there are no references to any 'Bar'
// in the entity graph
$foo->setDoStuff(...)
$em->flush();
但是,只有在代码的其他部分之前没有调用$foo->getBars()
或者加载“Bar”对象并计划稍后重用它们时,这才有效。