【问题标题】:lazy loading with doctrine one to one mapping使用教义一对一映射的延迟加载
【发布时间】:2013-01-04 02:35:40
【问题描述】:

我很难找到正确的结果。

我有一对一的映射。有两个表:

/**
* @ORM\Table(name="users")
* @ORM\Entity
*/
class Users {

   /**
    * @ORM\OneToOne(targetEntity="UsersSettings", mappedBy="user",cascade={"persist"})
   */
   private $userSetting;

   //getters and setters
}

/**
* @ORM\Table(name="notifications_settings")
* @ORM\Entity
*/
class UsersSettings {

   /**
   * @var Users
   *
   * @ORM\OneToOne(targetEntity="Users", inversedBy="userSetting")
   * @ORM\JoinColumns({
   *   @ORM\JoinColumn(name="user_id", referencedColumnName="id")
   * })
   */
   private $user;
}

每当我获取如下实体之一时:

$q = $this
            ->createQueryBuilder('u')
            ->select('u, r')
            ->leftJoin('u.roles', 'r')
            ->where('u.username = :username OR u.email = :email')
            ->setParameter('username', $username)
            ->setParameter('email', $username)
            ->getQuery();

Doctrine 立即加入我不想要的 usersSettings 实体:

SELECT t0.id AS id1, t0.username AS username2, t0.email AS email3, t0.password AS password4, t29.id AS id30, t29.is_notify_by_email AS is_notify_by_email31, t29.user_id AS user_id32 FROM users t0 LEFT JOIN users_settings t29 ON t29.user_id = t0.id WHERE t0.id = ?

OneToManyManyToOne 等其他类型的映射执行延迟加载,但在一对一映射的情况下,我无法配置为延迟加载。我怎样才能延迟加载这种关系?我正在使用学说 2.3 和 Symfony 2.1

【问题讨论】:

    标签: symfony doctrine-orm


    【解决方案1】:

    您可以在注释中使用fetch="EXTRA_LAZY" 启用extra lazy 关联加载。

    * @ORM\OneToOne(targetEntity="Users", inversedBy="userSetting", fetch="EXTRA_LAZY")
    

    【讨论】:

    • 在添加 extra_lazy 后仍然会加入表。我以前也试过,但没用
    • 您的服务器是否使用 APC?更新您的注释后,确保您的 apc 缓存已清除(如果需要,请重新启动 apache apache)。除非我重新启动 apache,否则某些学说注释不会生效。
    • 我之前启用了 APC,但是在开发过程中变得很头疼,所以没有启用 APC
    • 好吧,另一种选择是使用 DQL 并像这样创建您的查询:SELECT u FROM TestBundle:users u WHERE ... 这样您肯定不会获得联接。
    • OneToOne 关联对每个关联实体执行多个单独的查询时遇到了完全相同的问题(即使没有访问该实体中的属性)。添加 fetch="EXTRA_LAZY" 也没有为我解决问题(文档解释了原因)。 See more details on what worked.
    【解决方案2】:

    使用提示 HINT_FORCE_PARTIAL_LOAD 避免延迟加载。

    ...
    $qb->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
    

    【讨论】:

    • 这当然暴露了另一个问题 - 如果您只想获取未加入实体的键值(例如 id),则会引发异常。
    【解决方案3】:

    一对一关联的反面不能偷懒。

    解释:

    这是预期的行为。反面没有外键,因此无法决定是否代理它。我们必须查询关联的对象或加入它。请注意,这只影响单值关联的反面,即实际上只影响双向一对一关联的反面。

    为什么我们不能在没有 FK 的情况下创建代理?

    这很简单,在一侧是反面的一对多关联中,它包含一个集合。集合可能是空的,也可能不是,但总有一个集合,所以总是把一个代理集合放在那里很容易和正确。

    如果你有一个单值面,也就是逆面,你如何决定是否放一个代理对象呢?如果没有看到外键,您将无法区分:没有关联对象(因此将代理对象放置在适当的位置将完全是错误的)或存在一个并且它是哪种类型,如果涉及继承(因为放置输入错误类型的代理也是错误的)。

    查看https://github.com/doctrine/orm/issues/4389上的讨论

    【讨论】:

      【解决方案4】:

      解决方案 1: 你可以改变关系的拥有和反面

      解决方案 2: 您可以将反面的映射声明为 oneToMany 而不是 oneToOne,然后在 setter(如果您需要 setter)中将旧集合替换为仅包含一个元素的新集合,并在 getter 中返回集合的第一个元素(这是唯一的元素),类似这样:

      class Users {
      
          /**
          * 
          @ORM\OneToMany(targetEntity="UsersSettings", mappedBy="user")
          */
          private $userSetting;
         
          public function setUserSettings(UserSettings $setting): void
          {
              $setting->setUser($this);
              $this->userSettings = new ArrayCollection([$setting]);   
          }
      
          public function getUserSettings(): UserSettings
          {
              return $this->userSettings->first();
          }
      }
      

      在这种情况下,UserSettings 实体保持不变

      【讨论】:

        【解决方案5】:

        这个问题至少从 2010 年 2 月 21 日就已经知道了 - https://github.com/doctrine/orm/issues/4389。我们有 7 种方法可以解决,这里介绍的前 6 种 - https://stackoverflow.com/a/34353840/5735549

        最流行的解决方案的缺点:

        1. 更改关系的拥有方和反向方”(https://developer.happyr.com/choose-owning-side-in-onetoone-relation) - 将在拥有方产生额外的 id。例如,用户表将包含不必要的列,如 user_setting_id、user_cart_id、user_notification_settings_id 等。如果您决定对 user 和 user_settings 表使用相同的 id 策略,它看起来会更加错误。 users 表中的行将类似于“id=123, user_setting_id=123, user_cart_id=123, ...=123”。

        2. 将反面更改为 OneToMany 而不是 OneToOne” - 如果您以这种方式执行原则模式验证器(控制台原则:模式:验证)将产生错误,如“如果关联 UserSettings#user 是一个-to-one,那么反面 User#userSettings 也必须是一对一的”,symfony profiler 会在每个请求的“doctrine/entities mapping”部分显示这个错误,这让你对错误有更大的容忍度和学会忽略它们。

        另一个支持延迟加载的双向 OneToOne 关联解决方案是“OneToMany/ManyToOne 技巧” - 允许您延迟加载相关实体,将 id 存储在预期的一侧并且没有架构验证错误。

        下面是 user 和 userSetting 具有相同 id 策略的示例:

        class User
        {
            #[ORM\OneToMany(mappedBy: 'user', targetEntity: UserSettings::class, cascade: ['persist', 'remove'], fetch: 'LAZY', orphanRemoval: true)]
            #[ORM\JoinColumn(name: 'id', referencedColumnName: 'id', onDelete: 'CASCADE')]
            private Collection $userSettings;
        
            public function __construct()
            {
                $this->userSettings = new ArrayCollection;
            }    
            
            public function setUserSettings(UserSettings $setting): void
            {
                $this->userSettings = new ArrayCollection([$setting]);
            }
        
            public function getUserSettings(): ?UserSettings
            {
                return $this->userSettings->isEmpty() ? null : $this->userSettings->first();
            }
        }
        
        class UserSettings
        {
            #[ORM\Id]
            #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'userSettings')]
            #[ORM\JoinColumn(name: 'id', referencedColumnName: 'id', onDelete: 'CASCADE')]
            private User $user;
        
            public function __construct(User $user)
            {
                $this->user = $user;
                $this->user->setUserSettings($this);
            }
        
            public function getUser(): User
            {
                $this->user->setUserSettings($this);
        
                return $this->user;
            }
        }
        
        

        【讨论】:

        • 请不要只发布代码。尝试解释您的解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-01
        • 2014-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-11
        相关资源
        最近更新 更多