在ODM学说中加载相关文档会导致查询过多

时间:2019-03-14 11:12:16

标签: php mongodb symfony doctrine-odm

我一直在努力减少Web api上的数据库查询数量。 我的数据库有3个集合:playgroundwidgettoken

一个游乐场有很多小部件,一个小部件有一个令牌。每个关系都使用referencesOne / referenceMany

这是我的简化模型

/**
 * @MongoDB\Document()
 */
class Widget
{
    /**
     * @MongoDB\ReferenceOne(targetDocument="Token", inversedBy="widgets")
     */
    protected $token;

    /**
     * @MongoDB\ReferenceOne(targetDocument="Playground", inversedBy="widgets")
     */
    protected $playground;
}

/**
 * @MongoDB\Document()
 */
class Playground
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="playground")
     */
    protected $widgets;
}

/**
 * @MongoDB\Document()
 */
class Token
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="token")
     */
    protected $widgets;
}

我需要使用完整的游乐场及其所有小部件和令牌,但默认情况下,Doctrine会进行过多的查询:一个查询获取游乐场(确定),一个查询获取映射的所有小部件(确定),并且每个小部件,一次查询以获取令牌(不好)。有没有一种方法可以一次查询所有令牌,而不是一个一个地获取它们?

我看过素数,但似乎并不能解决我的问题...

除了使用查询生成器并手动合并所有对象以减少查询数量之外,还有其他方法吗?

编辑: 正如我在评论中所添加的那样,我要寻找的是将游乐场及其所有依赖项作为一个大对象进行json编码,然后将其返回到响应中。

我现在要做的是查询操场并对其进行编码,但是Doctrine以一种非有效的方式填充依赖项:首先是查询以获取Playgroung,然后是另一个查询以获取相关小部件,然后是每个小部件获取其令牌的一个查询。

由于一个游乐场可以包含数百个小部件,因此会导致数百个数据库查询。

我正在寻找一种方法来告诉Doctrine仅使用3个查询(一个用于获取playgroung,一个用于获取小部件,一个用于获取令牌)来获取所有这些数据。

2 个答案:

答案 0 :(得分:1)

更新:由于$playground中的ArrayCollection至少应包含所有小部件作为代理对象(或在访问时加载),因此以下内容应可用于获取所有必需的令牌...

由于文档管理器保留了所有托管对象,因此应该防止发生其他查询。 (请注意,execute中省略了分配)。

$qb = $dm->createQueryBuilder('Token')->findBy(['widget' => $playground->getWidgets()]);
$qb->getQuery()->execute();

this page on how to avoid doctrine orm traps - point 5启发

旧/原始答案

我对mongodb,tbh不太熟悉,但是根据教义的priming references,您应该可以通过以下方式为您的游乐场加水:

$qb = $dm->createQueryBuilder('Widget')->findBy(['playground' => $playground]);
$qb->field('token')->prime(true);
$widgets = $qb->getQuery()->execute();

但是,我可能错了。

答案 1 :(得分:0)

那又怎么样:

class Foo
{
    /** @var \Doctrine\ORM\EntityManagerInterface */
    private $entityManager;

    public function __construct(\Doctrine\ORM\EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function getAll(): array {
        $qb = $this->entityManager->createQueryBuilder();
        return $qb
            ->select('p,t,w')
            ->from(Playground::class, 'p')
            ->join(Widget::class, 'w')
            ->join(Token::class, 't')
            ->getQuery()
            ->getResult();
    }
}

至少对于mysql后端,这解决了“ n + 1问题”。不过,不确定MongoDB。