40

我有一个看起来像这样的查询:

我的用户实体具有如下所示的一对一关系:

/**
 * @var UserProfile
 *
 * @ORM\OneToOne(targetEntity="UserProfile",mappedBy="user")
 */
private $userProfile;

每当我进行查询以选择多个用户对象时,它都会为每个用户创建一个额外的选择语句来查询 UserProfile 数据,即使我没有通过 get 方法访问它。我并不总是需要 UserProfile 数据,而且我当然不想在每次显示用户列表时都加载这些数据。

知道为什么这些查询是在运行时执行的吗?

4

6 回答 6

27

以下是详细说明的解决方案:

https://groups.google.com/forum/#!topic/doctrine-user/fkIaKxifDqc

映射中的“获取”是一个提示,也就是说,如果 Doctrine 可能这样做,但如果不可能,显然它不会。从技术上讲,延迟加载的代理并不总是可行的。不可能的情况是:

1)从反向到拥有方的一对一(仅出现在双向一对一关联中)。前提条件 a) 不能满足。2)与层次结构的一对一/多对一关联,并且目标类具有子类(不是类层次结构中的叶)。不能满足上述前提条件 b)。

在这些情况下,代理在技术上是不可能的。

避免这个 n+1 问题的选项:

1) 通过 DQL 获取连接:“select c,ca from Customer join c.cart ca”。单查询但连接,然而,对一关联的连接相对便宜。

2)强制部分对象。没有额外的查询,但也没有延迟加载: $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)

3) 如果另一种结果格式(即 getArrayResult())足以满足用例的需求,这些也可以避免这个问题。

Benjamin 有一些关于自动批处理这些负载以避免 n+1 查询的想法,但这并不能改变代理并不总是可能的事实。

于 2014-03-07T15:11:35.613 回答
19

我花了很多时间寻找解决方案。对我来说,没有一个选项足够令人满意,但也许我可以通过以下解决方法列表为某人节省一些时间:

1) 改变拥有方和反方http://developer.happyr.com/choose-owning-side-in-onetoone-relation - 从数据库设计的角度来看,我不认为每次都是正确的。

2) 在 , 等函数中findfindAllOneToOne 的反面是自动连接的(它总是像 fetch EAGER 一样)。但在 DQL 中,它不像 fetch EAGER 那样工作,并且需要额外的查询。可能的解决方案是每次都加入反向实体

3) 如果替代结果格式(即getArrayResult())对于某些用例来说足够了,那也可以避免这个问题。

4) 将反面更改为 OneToMany - 只是看起来不对,可能是临时的解决方法。

5)强制部分对象。没有额外的查询,但也没有延迟加载:$query->setHint (Query::HINT_FORCE_PARTIAL_LOAD, true)-对我来说,这是唯一可能的解决方案,但并非没有代价:部分对象有点冒险,因为您的实体行为不正常。例如,如果您未在->select()所有关联中指定您将使用的用户,则可能会出现错误,因为您的对象不会满,所有未特别选择的关联都将为空

6)不映射反向双向 OneToOne 关联并使用显式服务或更主动的记录方法 - https://github.com/doctrine/doctrine2/pull/970#issuecomment-38383961 - 看起来 Doctrine 已关闭问题

于 2015-12-18T10:48:14.427 回答
12

似乎这是Doctrine 中的一个未解决问题,另请参阅

4.7.1. 为什么每次获取具有一对一关系的实体时都会执行额外的 SQL 查询?

如果 Doctrine 检测到您正在获取反向的一对一关联,它必须执行附加查询来加载此对象,因为它不知道是否没有这样的对象(设置为 null)或者它是否应该设置代理和这个代理有哪个 id。为了解决这个问题,目前必须执行一个查询来找出这个信息。

来源

于 2013-12-16T13:13:51.770 回答
2

正如@apfelbox 所解释的......现在没有修复它。

我结合唯一键选择了 OneToMany 解决方案:

User.php

/**
 * @ORM\OneToMany(targetEntity="TB\UserBundle\Entity\Settings", fetch="EXTRA_LAZY", mappedBy="user", cascade={"all"})
 */
protected $settings;

/**
 * @return \Doctrine\Common\Collections\Collection
 */
public function getSettings()
{
    return $this->settings;
}

Settings.php

/**
 * @ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", fetch="EXTRA_LAZY", inversedBy="settings")
 * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
 */
protected $user;

并确保 Settings.php 中的唯一性包括:

use Doctrine\ORM\Mapping\UniqueConstraint;

并添加唯一索引

/**
 * @ORM\Entity
 * @ORM\Table(name="user_settings", uniqueConstraints={@UniqueConstraint(name="user", columns={"user_id"})})
 */
class Settings

因此,当我想访问用户设置时,我只需要这样做(仅在该特定时刻触发一个查询)

$_settings = $user->getSettings()->current();

我认为是最干净的解决方案。

于 2015-10-18T13:39:18.733 回答
0

还有另一种选择(恕我直言,这是最好的) - 您可以使用单向 OneToOne。

在您的情况下 - 如果您很少使用 UserProfile - 在 UserProfile 中设置链接

/**
 * @var User
 *
 * @ORM\OneToOne(targetEntity="User")
 */
private $user;

只是不要在用户中映射它。您可以在需要时加载它。

如果您经常使用 UserProfile - 您可以将其作为 User 实体的一部分。

于 2016-02-09T21:00:50.653 回答
-4

根据参考您可以添加可选属性fetch

/**
 * @var UserProfile
 *
 * @ORM\OneToOne(targetEntity="UserProfile",mappedBy="user", fetch="LAZY")
 */
private $userProfile;
于 2012-09-11T08:54:24.183 回答