Php 条令在水合过程中添加额外查询,导致“正常”一对一和自引用关系出现n+1问题

Php 条令在水合过程中添加额外查询,导致“正常”一对一和自引用关系出现n+1问题,php,symfony,doctrine-orm,doctrine,select-n-plus-1,Php,Symfony,Doctrine Orm,Doctrine,Select N Plus 1,新闻是通过一对多的自我参照方式相互关联的,一条新闻是父新闻,可以有很多子新闻。更重要的是,每一条新闻都与事件和画廊有着正常的非自引用的一对一关系。当我运行简单DQL时: 从App\Entity\News n中选择n,其中n.parent=:id 然后使用getResults方法生成结果,并设置默认的对象值,在getResults方法内部的某个地方进行额外的查询 其中news_id=1和news_id=2是第一次查询所选新闻的子项 新闻也没有自我引用的一对多关系,我在这里忽略了它们,但我没有对它们

新闻是通过一对多的自我参照方式相互关联的,一条新闻是父新闻,可以有很多子新闻。更重要的是,每一条新闻都与事件和画廊有着正常的非自引用的一对一关系。当我运行简单DQL时:

从App\Entity\News n中选择n,其中n.parent=:id 然后使用getResults方法生成结果,并设置默认的对象值,在getResults方法内部的某个地方进行额外的查询

其中news_id=1和news_id=2是第一次查询所选新闻的子项

新闻也没有自我引用的一对多关系,我在这里忽略了它们,但我没有对它们提出额外的疑问。只有当where语句中涉及父关系时,才会出现问题

如何繁殖

画廊实体和事件相似,所以我在这里忽略了它

// News controller

public function index(NewsRepository $newsRepository, $slug)
{
        $news = $newsRepository->findOneBy(['slug' => $slug]);
        $newsRepository->getConnectedNews($news->getId());
}
有20个孩子的新闻最终有:20*2+1 n*r+1个查询,其中:

n 20是孩子的数量 R2是一对一关系的数量 1是基本查询 我想防止条令对一对一关系的额外质疑。是我犯了错误,还是我不想要的行为


总而言之,我只想得到所有没有一对一关系的自引用子对象,因为我没有要求检索它们,所以应该只使用一个查询来获取所有子对象的新闻,而不需要对每个检索到的“news”对象和每个it's one-to-one关系进行额外查询。

在这方面你有两个反向的one-one关系实体


反向OneToOne关系不能被信条延迟加载,并且很容易成为性能问题

如果您真的需要将这些关系映射到反向端,而不仅仅映射到拥有方,请确保显式地进行适当的连接,或者将这些关联标记为FETCH=EAGER,以便条令为您创建连接

例如,可以避免可怕的n+1问题的查询是:

SELECT n, g, e
    FROM App\Entity\News n
    LEFT JOIN n.gallery g
    LEFT JOIN n.event e
WHERE n.parent = :id

但为什么教义甚至试图加载一对一的关系呢?如果我在没有自引用关系的情况下执行查询,一切都正常,例如:从App\Entity\News n中选择n,其中n.id=:id我得到没有一对一关系的单个查询,这是正确的。如果我使用fetch=eager,它会通过连接两个其他关系来解决这个问题,就像在您的示例中一样,但在另一种情况下,它会导致另一个问题。基本上,我希望Doctrine在where语句中存在自引用关系时忽略该关系,就像在其他查询中一样。Doctrine不能延迟加载反向OneToOne关系-到目前为止,我在任何查询中都没有注意到该关系存在任何问题。当我将自引用的父子关系添加到新闻实体时,以及仅当我将父字段添加到where语句时,问题就出现了。反向onetoone关系的唯一问题是它们不能延迟加载,因此它们会触发其他查询,除非您显式加入或使用FETCH=EAGER。你可以阅读更多关于它的信息,例如。如果你应用了我在回答中提到的内容,你将不会得到额外的问题。谢谢你的链接和澄清。有一个很好的解决方法可以使用假一对多关系,这种关系假装是一对一,并且可以延迟加载。我更正了您的答案,因为在“选择”中,您必须提供所有实体来检索库和事件。只有加入而不加入是行不通的。我以前已经尝试过fetch=eager解决方案,但它在另一个测试用例中生成了更多的查询。事实证明,另一个多对多关系存在另一个问题。这就是为什么一开始我拒绝了你的两种解决方案。总而言之,一切都很清楚。
/**
 * @ORM\Entity(repositoryClass="App\Repository\EventRepository")
 * @Gedmo\SoftDeleteable()
 */
class Event
{
    use SoftDeleteableEntity;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
    /**
     * @ORM\OneToOne(targetEntity="App\Entity\News", inversedBy="event", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $news;
// News controller

public function index(NewsRepository $newsRepository, $slug)
{
        $news = $newsRepository->findOneBy(['slug' => $slug]);
        $newsRepository->getConnectedNews($news->getId());
}
// news repository

class NewsRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, News::class);
    }
    public function getConnectedNews($newsId) {
        $query = $this->createQueryBuilder('n')->andWhere('n.parent = :id')->setParameter('id', $newsId);
        return $query->getQuery()->getResult(AbstractQuery::HYDRATE_OBJECT);
    }
}
SELECT n, g, e
    FROM App\Entity\News n
    LEFT JOIN n.gallery g
    LEFT JOIN n.event e
WHERE n.parent = :id