Doctrine2 多对一关联不会使用 JOIN 查询

Posted

技术标签:

【中文标题】Doctrine2 多对一关联不会使用 JOIN 查询【英文标题】:Doctrine2 many-to-one association won't use JOIN query 【发布时间】:2010-11-12 18:23:41 【问题描述】:

我有一个 Car 实体,它与实体 Owner 具有多对一关系。如果我选择所有汽车,Doctrine 会对Car 表进行一次查询,然后对Owner 表进行一次查询每辆车。因此,获取 N 辆汽车变成了 N+1 个查询,而不是 CarOwner 表之间的单个 JOIN 查询。

我的实体如下:

/** @Entity */
class Car 

  /** @Id @Column(type="smallint") */
  private $id;

  /** @ManyToOne(targetEntity="Owner", fetch="EAGER")
      @JoinColumn(name="owner", referencedColumnName="id") */
  private $owner;

  public function getId()     return $this->id; 
  public function getOwner()  return $this->owner; 


/** @Entity */
class Owner 

  /** @Id @Column(type="smallint") */
  private $id;

  /** @Column(type="string") */
  private $name;

  public function getName()  return $this->name; 

如果我想列出汽车及其所有者,我会这样做:

$repo = $em->getRepository('Car');
$cars = $repo->findAll();

foreach($cars as $car) 
  echo 'Car no. ' . $car->getId() . 
       ' owned by ' . $car->getOwner()->getName() . '\n';

现在这一切都运行良好,除了 Doctrine 为每辆车发出查询。

SELECT * FROM Car;
SELECT * FROM Owner WHERE id = 1;
SELECT * FROM Owner WHERE id = 2;
SELECT * FROM Owner WHERE id = 3;
....

当然我希望我的查询日志看起来像这样:

SELECT * FROM Car JOIN Owner ON Car.owner = Owner.id;

无论我有fetch="EAGER" 还是fetch="LAZY" 都无所谓,即使我在两个实体之间使用 JOIN 进行自定义 DQL 查询,$car->getOwner() 仍然会导致 Doctrine 查询数据库(除非我使用 EAGER,在这种情况下,$repo->findAll() 会导致所有这些)。

我在这里是不是太累了,这就是它应该工作的方式 - 或者有没有一种聪明的方法可以强制 Doctrine 执行 JOIN 查询?

【问题讨论】:

【参考方案1】:

至少在 1.x Doctrine 中,如果要查询相关对象,则必须使用 DQL。对于您的情况,DQL 查询看起来像这样:

//Assuming $em is EntityManager
$query = $em->createQuery('SELECT c, o FROM Car c JOIN c.owner o');
$cars = $query->execute();

【讨论】:

看起来在 2.x 中也是如此。我试过查询SELECT c FROM Car c JOIN c.owner o,它仍然进行了 N+1 个查询,但你的工作正常。非常感谢!我想我只能忍受不使用 findXxx 方法。 对于这样的方便方法,我建议创建一个“服务层”或“存储库”样式类来处理加载模型等任务。这样,您还可以轻松地重用查询,而不必到处复制 DQL 逻辑。【参考方案2】:

首先运行一个 DQL 查询,您可以在其中选择所有与车主连接 (DQL JOIN) 的汽车。将所有者放入select()

// preload cars
$qb = $em->createQueryBuilder()
        ->select('car, owner')
        ->from('\Entity\Car', 'car')
        ->leftJoin('c.owner',  'owner');

    $query = $qb->getQuery();

    // the following seems not needed, but I think it depends on the conf
    $query->setFetchMode("\Entity\Car", "owner", "EAGER");

    $query->execute(); //you don't have to use this result here, Doctrine will keep it

然后,Doctrine 2 将执行 JOIN(通常更快,因为它需要更少的数据库查询,具体取决于记录的数量)。 现在启动你的foreach,Doctrine 会在内部找到实体,当你需要owner 时它不会运行单个查询。

在每次更改之前/之后监控查询的数量(例如 mysql 常规日志)

【讨论】:

【参考方案3】:

您的查询...

$car->getOwner() // "go and fetch this car's owner"

... 在 foreach 循环中,因此它肯定会多次发出查询。

如果您正在编写自定义 DQL 来处理这个问题,$car->getOwner() 根本不应该出现在其中。这是 Car 类的一个函数。您将编写的自定义 DQL 将模仿您指出的确切 SQL 查询并有效地完成您的联接。

【讨论】:

+1 感谢您的回复。只要所有者成员是 EAGERly 加载的,在循环内执行 $car->getOwner() 就不是问题。如果这只是发生在延迟获取中,我会理解,但是通过急切加载它并不理想。请参阅我对 Jani Hartikainen 关于 DQL 的回复 :-) @Frode:确实,现在更好地理解您的问题。还没有遇到过这种情况,并且希望从急切加载中获得与您相同的效率。看起来需要 DQL。

以上是关于Doctrine2 多对一关联不会使用 JOIN 查询的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate ManyToOne Mappings 多对一关联映射

Hibernate 多对一关联查询

Doctrine 2 - 多对一关系的外键不允许空值

mybatis多对一关联

mybatis--多对一关联

Mybatis_多对一关联查询