// Device entity /** * @ORM\OneToMany(targetEntity="AppBundle\Entity\Event",mappedBy="device") */ protected $events; // Event entity /** * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Device",inversedBy="events") */ protected $device;
问题出现是因为Device是单表继承实体
* @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="device_class_type",type="string")
每次我获取并迭代一些Event实体时,总是急切地获取$device.发生这种情况是因为它是relative documentation中报告的STI实体
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity,it is preferable for performance reasons
that it be a leaf entity in the inheritance hierarchy,(ie. have no
subclasses). Otherwise Doctrine CANNOT create proxy instances of this
entity and will ALWAYS load the entity eagerly.
现在有另一个名为Gateway的实体与Device和Event有关系:
/** * @ORM\OneToMany(targetEntity="AppBundle\Entity\Device",mappedBy="gateway") */ protected $devices; /** * @ORM\OneToMany(targetEntity="targetEntity="AppBundle\Entity\Event",mappedBy="gateway") */ protected $events; public function getEvents(): Collection { return $this->events; }
当然,每当我遍历$gateway-> getEvents()时,所有相关事件设备都会被急切地获取.即使我没有获得任何$device信息,也会发生这种情况 – 一个空的foreach就足以让Doctrine为每个对象执行1个查询来获取相关的$device
foreach ($gateway->getEvents() as $event) {}
现在我知道我可以使用QueryBuilder设置一个不同的水合模式,避免$device fetching
return $this->getEntityManager()->createQueryBuilder() ->select('e') ->from('AppBundle:Event','e') ->where('e.gateway = :gateway') ->setParameter('gateway',$gateway) ->getQuery()->getResult(Query::HYDRATE_SIMPLEOBJECT);
但我想直接在Gateway实体中做到这一点.
那么可以直接在Gateway实体类中水合Gateway->事件吗?
您有一个循环引用,其中一个节点(Device)将强制FETCH EAGER.更糟糕的是,其中一个节点(Gateway)就像其他两个节点之间的ManyToMany连接表一样,导致FETCH EAGER在近无限循环(或至少大块相关数据)中加载所有内容.
+──< OneToMany >──+ ManyToOne >──< ManyToMany +──+ OneToOne ┌──────< Gateway >──────┐ │ │ + + Event +──────────────< Device*
正如您所看到的,当设备执行EAGER提取时,它将收集许多网关,因此会收集许多事件,因此会有许多设备,因此会有更多网关等.获取EAGER将继续运行,直到填充所有引用.
通过建立自己的保湿器来防止“EAGER”保湿.
构建自己的保湿器需要一些仔细的数据操作,但对于您的用例可能会有些简单.记得用Doctrine注册您的保湿器,并将其作为参数传递给$query-> execute([],’GatewayHydrator’);
class GatewayHydrator extends DefaultEntityHydrator { public function hydrateResultSet($stmt) { $data = $stmt->fetchAll(PDO::FETCH_ASSOC); $class = $this->em->getClassMetadata(Gateway::class); $gateway = $class->newInstance(); $gateway->setName($data[0]['gateway_name']); // example only return $gateway; } }
或者,从设备到网关删除映射字段
删除$gateway =>来自Device的网关映射和来自Gateway->设备映射的mappedBy =“gateway”,Device将从Doctrine的角度有效地成为一片叶子.这将避免该参考循环,但有一个缺点:必须手动设置Device-> gateway属性(可能在Gateway和Event setDevice方法中).