Doctrine一对一关联:主表两字段同时关联同一address表如何实现
这个需求完全可以通过Doctrine注解映射实现,你遇到的问题是关联注解配置错误导致的,以下是修正方案:
错误原因梳理
- 关联方向配置错误:
sales_order表持有shipping_address、billing_address两个外键指向address表的主键,因此SalesOrder是关联的拥有方,应该使用@JoinColumn指定外键映射,而非用mappedBy(mappedBy仅用于不持有外键的反向关联端) Address实体的主键字段上不需要配置关联注解,你写在$id上的两个@OneToOne属于无效配置,逻辑上也不可能让主键同时关联到SalesOrder的两个不同字段- 如果不需要从
Address反向查询关联的订单,Address实体完全不需要配置任何和SalesOrder相关的关联属性
修正后的实体代码
SalesOrder 实体
class SalesOrder { /** * @ORM\Id * @ORM\Column(name="order_id", type="string", length=255) */ private $orderId; /** * @ORM\ManyToOne(targetEntity="Address") * @ORM\JoinColumn(name="shipping_address", referencedColumnName="id", nullable=false) */ private $shippingLine; /** * @ORM\ManyToOne(targetEntity="Address") * @ORM\JoinColumn(name="billing_address", referencedColumnName="id", nullable=false) */ private $billingLine; public function getOrderId() { return $this->orderId; } public function getShippingLine() { return $this->shippingLine; } public function getBillingLine() { return $this->billingLine; } }
注:这里用
ManyToOne而非OneToOne是因为同一个地址可以关联多个订单,符合常规业务逻辑,如果你的业务确实要求一个地址只能对应一个订单,换成OneToOne也可以正常运行。
Address 实体
class Address { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(name="id", type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $city; /** * @ORM\Column(type="string", length=255) */ private $country; public function getCity() { return $this->city; } public function getCountry() { return $this->country; } }
查询实现
懒加载模式(简单场景)
直接用find方法查询订单后,调用getShippingLine()、getBillingLine()时Doctrine会自动触发查询加载对应地址数据:
$orderDetails = $em->getRepository(SalesOrder::class)->find('abc1'); // 直接调用getter即可拿到地址对象 var_dump($orderDetails->getShippingLine()->getCity()); // 输出 city 1
联查模式(和你提供的原生SQL效果完全一致,避免N+1查询)
如果需要一次查询就返回订单+两个地址的所有数据,用QueryBuilder实现联查即可:
$qb = $em->createQueryBuilder(); $orderDetails = $qb->select('o', 's', 'b') ->from(SalesOrder::class, 'o') ->join('o.shippingLine', 's') ->join('o.billingLine', 'b') ->where('o.orderId = :orderId') ->setParameter('orderId', 'abc1') ->getQuery() ->getSingleResult();
此时序列化后得到的结构就会包含shippingLine和billingLine的完整地址数据,和你原生SQL的查询结果完全一致。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

