【问题标题】:Doctrine2 one-to-one relation auto loads on queryDoctrine2 一对一关系在查询时自动加载
【发布时间】:2012-09-11 04:45:24
【问题描述】:

我的查询如下所示:

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

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

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

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

【问题讨论】:

    标签: doctrine-orm


    【解决方案1】:

    这里是解决方案的详细说明:

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

    映射中的

    “fetch”是一个hint,即如果可能的话 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, 真的)

    3) 如果另一种结果格式(即 getArrayResult())是 对于用例来说足够了,这些也可以避免这个问题。

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

    【讨论】:

      【解决方案2】:

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

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

      2) 在findfindAll 等函数中,OneToOne 中的反面自动连接(总是像 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 解决了这个问题

      【讨论】:

        【解决方案3】:

        看来这是open issue in Doctrine,另见

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

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

        Source

        【讨论】:

          【解决方案4】:

          正如@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();
          

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

          【讨论】:

            【解决方案5】:

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

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

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

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

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

            【讨论】:

              【解决方案6】:

              根据reference可以添加可选属性fetch

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

              【讨论】:

              • 这似乎是完全正确的解决方案,但奇怪的是这并没有解决问题。
              • 也许您在代码的某些奇怪部分调用了配置文件。
              • 我也是这么想的,但我很确定我必须通过主要的 getUserProfile getter 才能实现。我在我的代码中找不到任何证据。
              • 帖子较旧,但我发表评论是因为我遇到了同样的问题,而且似乎还没有解决。我查看了我所有的代码,我从来没有在任何地方调用关系!无法修复:s
              • 这里也一样...找到解决方案了吗?
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-09-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多