Doctrine关联查询按语言ID过滤关联实体异常问题
我有Dimension和DimensionLang两个实体,一个Dimension对应多个DimensionLang条目。我想获取所有Dimension及其对应符合指定语言ID的DimensionLang列表,但目前查询后遍历dimension->getDimensionLangs()时,仍然返回该Dimension关联的所有语言的DimensionLang条目,即便查询里加了idLang的过滤条件。使用的Doctrine版本是2.6。
Dimension实体代码
/** * @ORM\Table(name="ps_ppbs_dimension") * @ORM\Entity(repositoryClass=DimensionRepository::class) * @ORM\HasLifecycleCallbacks */ class Dimension { /** * @ORM\Id * @ORM\Column(name="id_dimension", type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private ?int $id = null; /** * @ORM\OneToMany(targetEntity=DimensionLang::class, cascade={"persist", "remove"}, mappedBy="dimension") */ private $dimensionLangs; // getters and setters }
DimensionLang实体代码
/** * @ORM\Table(name="ps_ppbs_dimension_lang") * @ORM\Entity(repositoryClass=DimensionRepository::class) * @ORM\HasLifecycleCallbacks */ class DimensionLang { /** * @ORM\Id * @ORM\ManyToOne(targetEntity=Dimension::class, inversedBy="dimensionLangs") * @ORM\JoinColumn(name="id_dimension", referencedColumnName="id_dimension", nullable=false) */ private ?Dimension $dimension = null; /** * @ORM\Id * @ORM\Column(type="integer", options={"unsigned": true}) */ private ?int $idLang = null; /** * @ORM\Column(type="string", length=128) */ private string $displayName; /** * @ORM\Column(type="string", length=512) */ private string $image; // Getters and setters }
尝试的查询代码
$dimensions = $this->createQueryBuilder('d') ->join('PrestaShop\Module\ProductPriceBySize\Entity\DimensionLang', 'dl', 'WITH', 'd.id = dl.dimension') ->where('dl.idLang = :idLang') ->setParameter('idLang', 1) ->getQuery() ->getResult(); // Iterate over dimensions foreach ($dimensions as $dimension) { echo "Dimension ID: " . $dimension->getId() . "<br>"; // Iterate over DimensionLang entries foreach ($dimension->getDimensionLangs() as $dimensionLang) { dump($dimensionLang); } }
出现这个问题的核心原因是:当前的join仅用于过滤Dimension实体(只保留存在对应语言DimensionLang的Dimension),但Doctrine默认会在你访问dimensionLangs集合时,重新执行查询加载该Dimension关联的所有DimensionLang条目,不会复用之前的过滤条件。
针对Doctrine 2.6,有以下几种可行的解决方式:
1. 使用FETCH JOIN + 显式选择关联数据
修改查询,使用leftJoin并通过addSelect把过滤后的DimensionLang数据一并加载,Doctrine会将符合条件的dl数据直接注入到Dimension的dimensionLangs集合中:
$dimensions = $this->createQueryBuilder('d') ->leftJoin('d.dimensionLangs', 'dl', 'WITH', 'dl.idLang = :idLang') ->addSelect('dl') // 显式选择关联的DimensionLang数据 ->setParameter('idLang', 1) ->getQuery() ->getResult();
2. 给关联字段设置EXTRA_LAZY加载策略
在Dimension实体的dimensionLangs关联注解中添加fetch="EXTRA_LAZY",让Doctrine延迟加载集合子集,而非一次性加载全部:
/** * @ORM\OneToMany( * targetEntity=DimensionLang::class, * cascade={"persist", "remove"}, * mappedBy="dimension", * fetch="EXTRA_LAZY" * ) */ private $dimensionLangs;
该方式建议结合第一种方式使用,避免额外的查询开销。
3. 查询返回关联数组对象
如果不需要完整的Dimension实体对象,可以直接查询返回包含Dimension和对应DimensionLang的数组,完全由SQL控制结果:
$result = $this->createQueryBuilder('d') ->select('d.id, dl.displayName, dl.image') ->join('d.dimensionLangs', 'dl') ->where('dl.idLang = :idLang') ->setParameter('idLang', 1) ->getQuery() ->getArrayResult();
注意事项
- 若要保留所有
Dimension(哪怕无对应语言的条目),使用leftJoin;若仅需保留有对应语言条目的Dimension,可改用innerJoin。 - 若启用了Doctrine缓存,需确保缓存策略不会影响过滤结果,必要时清除相关缓存。
内容的提问来源于stack exchange,提问作者Musaffar Patel

