与一个实体的多个 OneToMany 关系

Posted

技术标签:

【中文标题】与一个实体的多个 OneToMany 关系【英文标题】:Multiple OneToMany relations to one Entity 【发布时间】:2015-10-19 13:49:38 【问题描述】:

曾几何时,在 Symfony 大地深处的黑暗深渊中,有一个沮丧的程序员。他尝试了又尝试,但不知何故,邪恶的教义一次又一次地袭击。反派JoinsAssociative tablesOne-to-Many/Many-to-One 也让他很难受。然后,在一个下午晚些时候,*** 的社区来救援了。

童话故事已经够多了。我的问题是我有三个表都应该引用同一个表来获取附件。

- Mail
- Order
- Ticket

这三个实体中的每一个都可以有附件。所以我做了一个附件实体。

现在,我的数据库包含以下内容

Table: mails
- id
- from
- to
- message

Table attachments
- id
- name
- path

Table: orders
- id
- ...

Table: tickets
- id
- name
- description
- ...

Table attachment_associations
- id
- type
- parent_id
- attachment_id

我想做的是能够将订单、票证和邮件映射到同一个附件表。

但是,我对如何在教义中做到这一点感到困惑。

更新

我尝试使用以下方法。这似乎得到了我正在寻找的记录。但我不知道如何使用这种方法自动创建、更新或删除关联表(连接表)中的记录。

/**
 * @ORM\ManyToMany(targetEntity="\...\...\Entity\Attachment")
 * @ORM\JoinTable(name="attachment_associations",
 *      joinColumns=@ORM\JoinColumn(name="parentId", referencedColumnName="id"),
 *      inverseJoinColumns=
 *          @ORM\JoinColumn(name="attachmentId", referencedColumnName="id")
 *     
 * )
 */
protected $attachments;

另一个更新

如果我删除邮件、订单或工单,所有相应的附件也会被删除吗?

【问题讨论】:

这是一个很好的起点doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/… 只需创建三个关联映射 - 它们当然可以是两列的null,而第三列设置为附件所属的实体。 你有没有想过实现一个映射的超类来管理关联。邮件、订单和门票是否从那里延伸出来?这会影响性能,但它可能是您尝试做的事情的一种方法。 @Yoshi 我对整个场景都很陌生。你能详细说明一下吗? 这是this question 的副本,您可以在其中找到很多关于如何做到这一点的好例子和坏例子。 【参考方案1】:

一种非常容易做到这一点的方法是实现一个mapped super class 和class table inheritance,您的其他实体是从该class table inheritance 扩展而来的。

虽然有performance implications,但您必须针对您的具体项目进行判断。

这是一个简单的例子:


映射的超类

<?php

namespace AcmeBundle\Model;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity()
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 */
abstract class SuperClass

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    protected $id;

    /**
     * @var Attachment[]
     *
     * @ORM\ManyToMany(targetEntity="Attachment", mappedBy="parents")
     */
    protected $attachments = [];

    /**
     * Constructor
     */
    public function __construct()
    
        $this->attachments = new ArrayCollection();
    

    // put setters/getters for $attachments here

和附件管理关联。

<?php

namespace AcmeBundle\Model;

use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity()
 */
class Attachment

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var SuperClass
     *
     * @ORM\ManyToMany(targetEntity="SuperClass", inversedBy="attachments")
     */
    private $parents;

    /**
     * Constructor
     */
    public function __construct()
    
        $this->parents = new ArrayCollection();
    

实体只是扩展超类

<?php

namespace AcmeBundle\Model;

use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity()
 */
class Ticket extends SuperClass


【讨论】:

ManyToOne 不应该是 OneToMany 吗?一封邮件可以有多个附件。 是的,这可能是错误的。您不想每个实体有多个附件,而每个附件可以分配给多个实体吗?如果是这样,那么 m-n 应该是正确的类型。我会更新答案。 但是无论如何,以此为起点,您可以非常轻松地更改关联,而不会对扩展实体产生任何影响。 @Wilt 这不正确。所有实体都有自己的表。见doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/… @Yoshi,我会改写。他们将有一个带有id 的公用表,这意味着对于每个查询,您还必须搜索公用表,并且该表包含所有3 种类型的实体MailOrderTicket,这对性能不利。此外,每次解析实体时都需要大量连接。所有这一切都是为了一个简单的附件......!?恕我直言,这不是一个好主意【参考方案2】:

您可以通过将单向一对多与连接表结合使用来将所有附件放在一个表中。 In doctrine this is done with a unidirectional Many-To-Many with a unique constraint on the join column。这意味着一个带有附件的表,但连接到每个父级的连接表不同。

此解决方案的缺点是它是单向的,这意味着您的附件不知道关系的拥有方。

完整的代码如下所示:

AttachmentsTrait 带有用于附件的 setter 和 getter,以防止代码重复:

<?php

namespace Application\Entity;

use Doctrine\Common\Collections\Collection; 

/**
 * @property Collection $attachments
 */
trait AttachmentTrait

    /**
     * Add attachment.
     *
     * @param Attachment $attachment
     * @return self
     */
    public function addAttachment(Attachment $attachment)
    
        $this->attachments[] = $attachment;

        return $this;
    

    /**
     * Add attachments.
     *
     * @param Collection $attachments
     * @return self
     */
    public function addAttachments(Collection $attachments)
    
        foreach ($attachments as $attachment) 
            $this->addAttachment($attachment);
        
        return $this;
    

    /**
     * Remove attachment.
     *
     * @param Attachment $attachments
     */
    public function removeAttachment(Attachment $attachment)
    
        $this->attachments->removeElement($attachment);
    

    /**
     * Remove attachments.
     *
     * @param Collection $attachments
     * @return self
     */
    public function removeAttachments(Collection $attachments)
    
        foreach ($attachments as $attachment) 
            $this->removeAttachment($attachment);
        
        return $this;
    

    /**
     * Get attachments.
     *
     * @return Collection
     */
    public function getAttachments()
    
        return $this->attachments;
    

您的Mail 实体:

<?php

namespace Application\Entity;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;    

class Mail

    use AttachmentsTrait;

    /**
     * @var integer
     * @ORM\Id
     * @ORM\Column(type="integer", nullable=false)
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Attachment")
     * @ORM\JoinTable(name="mail_attachments",
     *     inverseJoinColumns=@ORM\JoinColumn(name="attachment_id", referencedColumnName="id"),
     *     joinColumns=@ORM\JoinColumn(name="mail_id", referencedColumnName="id", unique=true)
     * )
     */
    $attachments;

    /**
     * Constructor
     */
    public function __construct()
    
        $this->attachments = new ArrayCollection();
    

您的Order 实体:

<?php

namespace Application\Entity;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;    

class Order

    use AttachmentsTrait;

    /**
     * @var integer
     * @ORM\Id
     * @ORM\Column(type="integer", nullable=false)
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Attachment")
     * @ORM\JoinTable(name="order_attachment",
     *     inverseJoinColumns=@ORM\JoinColumn(name="attachment_id", referencedColumnName="id"),
     *     joinColumns=@ORM\JoinColumn(name="order_id", referencedColumnName="id", unique=true)
     * )
     */
    $attachments;

    /**
     * Constructor
     */
    public function __construct()
    
        $this->attachments = new ArrayCollection();
    

您的Ticket 实体:

<?php

namespace Application\Entity;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;    

class Ticket

    use AttachmentsTrait;

    /**
     * @var integer
     * @ORM\Id
     * @ORM\Column(type="integer", nullable=false)
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Attachment")
     * @ORM\JoinTable(name="ticket_attachment",
     *     inverseJoinColumns=@ORM\JoinColumn(name="attachment_id", referencedColumnName="id"),
     *     joinColumns=@ORM\JoinColumn(name="ticket_id", referencedColumnName="id", unique=true)
     * )
     */
    $attachments;

    /**
     * Constructor
     */
    public function __construct()
    
        $this->attachments= new ArrayCollection();
    

编辑:

如果您真的希望Attachment 了解另一方,您可以在两者之间添加一个额外的实体来管理它。这意味着将连接表本身制作为实体,例如:MailAttachmentTicketAttachmentOrderAttachment

【讨论】:

这不需要 4 个不同的表吗? mail_attachmentsorder_attachmentsticket_attachments @peter。是的,您将拥有三个连接表和一个包含所有附件的附件表。它们在关联定义中。 @Peter 由于外键约束,您无法对带有partent_id 的表执行您想要的操作。 parent_id 只能指向(成为约束)一个实体,不能同时指向三个不同的 id mail_idorder_idticket_id @Peter 您可以使用一个具有共享标识符id 的基本实体来执行此操作,因此您可以使用parent_id 指向该字段,但我绝对不推荐这样做。 Mail、Ticket 和 Order 应该在不同的表中,而不是在同一个表中。 我还需要附件实体吗?

以上是关于与一个实体的多个 OneToMany 关系的主要内容,如果未能解决你的问题,请参考以下文章

将实体与 onetomany 映射和 @version 字段合并会导致删除先前的映射

OneToMany 和 ManyToMany 单向关系的区别

JPA 与同一实体的两个单向 @OneToMany 关系导致重复条目

不审核 OneToMany 关系的缺点是啥?

如何在一个 JPQL 查询中使用多个 JOIN FETCH

JPA与同一实体的两个单向@OneToMany关系导致重复输入