具有继承映射的多对一自引用

Posted

技术标签:

【中文标题】具有继承映射的多对一自引用【英文标题】:Many-To-One, Self-referencing with Inheritance Mapping 【发布时间】:2015-11-30 10:32:09 【问题描述】:

我有一些具有共同关系和属性的实体。所以,我想使用继承映射来简化我的架构。

我创建了一个 BaseData 映射超类,并让我的其他实体扩展它。这个 BaseData 类在每个实体中都有我需要的公共关系。

它适用于多对一关系,例如

/**
 * @ORM\MappedSuperclass
 */
class BaseData


    /**
    * @ORM\ManyToOne(targetEntity="Service")
    * @ORM\JoinColumn(name="service_id", referencedColumnName="id")
    */  
    protected $service;

但是自引用会变得有点棘手。

例如,因为我想创建一个父引用,所以我尝试了:

/**
 * @ORM\MappedSuperclass
 */
class BaseData


    /**
    * @ORM\ManyToOne(targetEntity="BaseData")
    * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
    */
    protected $parent;

显然,当我尝试查询此实体时,它会导致 TableNotFoundException:QLSTATE[42S02]: Base table or view not found: 1146 Table 'project.base_data' doesn't exist

所以,我尝试了 AssociationOverrides,但似乎不允许更改目标实体。

那么,有没有办法在 MappedSuperclass 上构建一些自引用?顺便说一句,这有意义吗?

提前非常感谢!

更新

这里是答案:

我按计划在我的 BaseData mappedSuperClass 中定义了protected $parentprotected $children。我用我需要的其他信息对它们进行了注释。例如:

/**
 * @ORM\MappedSuperclass
 */
class BaseData


    /**
    * @Datagrid\Column(field="parent.id", title="datagrid.parent_id", visible=false, safe=false)
    * @Serializer\Expose
    * @Serializer\Groups("foo")
    */
    protected $parent;

    /**
     * @Serializer\Expose
     * @Serializer\Groups("elastica")
     */
    protected $children;

然后,我添加事件 loadClassMetadata 的 ORM 关系。

/**
 * @param LoadClassMetadataEventArgs $eventArgs
 */
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)

    // the $metadata is all the mapping info for this class
    $classMetadata  = $eventArgs->getClassMetadata();
    $reflObj = new \ReflectionClass($classMetadata->name);
    if($reflObj) 
        if ($reflObj->isSubclassOf('CoreBundle\Entity\BaseData')) 
            $fieldMapping = array(
                'targetEntity'  => $classMetadata->name,
                'fieldName'     => 'parent',
                'inversedBy'    => 'children',
                'JoinColumn'    => array(
                    'name'                  => 'parent_id',
                    'referencedColumnName'  => 'id',
                    'nullable'              => true,
                    'onDelete'              => 'SET NULL',
                ),
            );

            $classMetadata->mapManyToOne($fieldMapping);


            $fieldMapping = array(
                'fieldName'     => 'children',
                'targetEntity'  => $classMetadata->name,
                'mappedBy'      => 'parent',
            );
            $classMetadata->mapOneToMany($fieldMapping);
        
    

注册活动,就是这样。

现在,扩展 BaseData 超类的每个类都获得了关系。例如,php app/console doctrine:generate:entities MyBundle 将在 SubClass 实体中生成以下代码:

/**
 * Set parent
 *
 * @param \MyBundle\Entity\Subclass $parent
 *
 * @return Subclass
 */
public function setParent(\MyBundle\Entity\Subclass $parent = null)

    $this->parent = $parent;

    return $this;


/**
 * Get parent
 *
 * @return \MyBundle\Entity\Subclass
 */
public function getParent()

    return $this->parent;


/**
 * Add child
 *
 * @param \MyBundle\Entity\Subclass $child
 *
 * @return Subclass
 */
public function addChild(\MyBundle\Entity\Subclass $child)

    $this->children[] = $child;

    return $this;


/**
 * Remove child
 *
 * @param \MyBundle\Entity\Subclass $child
 */
public function removeChild(\MyBundle\Entity\Subclass $child)

    $this->children->removeElement($child);


/**
 * Get children
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getChildren()

    return $this->children;

【问题讨论】:

您只能对子类进行自引用,因为 MappedSupperclass 本身并不是一个实体。来自文档:映射的超类不能是实体,它不是可查询的,并且由映射的超类定义的持久关系必须是单向的(仅具有拥有方)。这意味着在映射的超类上根本不可能进行一对多关联。此外,只有当映射的超类目前仅在一个实体中使用时,多对多关联才是可能的。 我确实猜到了,但我的目标是让自引用对每个子类都有效。我的意思是,通过写targetEntity="BaseData",我希望每个子类都使用自己的名称。所以,我想,我必须手动在每个子类中进行父自引用,不是吗?感谢您的帮助。 没问题!由于 @MappedSuperclass 本身不是实体,是的,我想,你只能在子类中执行此操作。 p.s.恕我直言,@MappedSuperclass 对于所有类型的字典都非常有用——例如,在上一个项目中,我有一个 BaseDictionaryDocument,有两个字段 id 和 sysName。所有字典看起来都像class BlahBlahDocument extends BaseDictionaryDocument ,当然还有一个顶部带有表/集合名称的注释。 嗯,所以我想我会手动完成...我想将它设置在一个超类上,所以我将在其他方向进行更多挖掘。再次感谢。 【参考方案1】:

您可以删除映射@ORM\ManyToOne(targetEntity="BaseData") 并在事件 loadClassMetadata 上创建事件侦听器。 (我没有测试下面的代码,这只是一个起点)像这样的东西:

class TestEvent

    public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
    
        $classMetadata = $eventArgs->getClassMetadata();
        $class = $classMetadata->getName();
        $fieldMapping = array(
            'fieldName' => 'parent',
            'targetEntity' => $class,
        );
        $classMetadata->mapManyToOne($fieldMapping);
    

需要注意的重要一点是,侦听器将侦听应用程序中的所有实体。

见Doctrine docs about events 还有How to register event listener in symfony2

【讨论】:

我尝试了您的解决方案,并设法使其按我的意愿工作。我将更新我的问题以集成 anwser。 我很高兴它有帮助

以上是关于具有继承映射的多对一自引用的主要内容,如果未能解决你的问题,请参考以下文章

多对一映射不允许属性

hibernate多对多映射中间表有多余字段问题该如何映射

Hibernate,关系映射的多对一单向关联多对一双向关联一对一主键关联一对一外键关联多对多关系关联

如何正确映射与@IdClass 的多对一关系?

spring boot jpa中的多对一映射中的外键未在子表中更新

Hibernate框架学习之注解配置关系映射