如何将继承策略与 JPA 注释和 Hibernate 混合使用?

Posted

技术标签:

【中文标题】如何将继承策略与 JPA 注释和 Hibernate 混合使用?【英文标题】:How to mix inheritance strategies with JPA annotations and Hibernate? 【发布时间】:2011-04-24 07:44:00 【问题描述】:

根据Hibernate 参考文档,在使用 Hibernate 的 XML 元数据时应该可以混合不同的继承映射策略:http://docs.jboss.org/hibernate/stable/core/reference/en/html/inheritance.html#inheritance-mixing-tableperclass-tablepersubclass

但是,Hibernate Annotations Reference Guide 的相应部分并未涵盖:http://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#d0e1168

另一方面,JavaDocs 建议混合继承策略应该是可能的。例如在 javax.persistence.DiscriminatorColumn 它说:​​

策略和鉴别器列仅在实体类层次结构的根或应用不同继承策略的子层次结构中指定。


以下是我尝试实现的映射的示例。我想在层次结构的根附近使用 table-per-sub-class 映射,但在叶子附近更改为 table-per-class-hierarchy 映射。下面是一些示例代码:

@Entity
@Inheritance( strategy = InheritanceType.JOINED )
public abstract class A implements Serializable

    @Id
    private String id;

    // other mapped properties...


@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
public class BB extends A

    // other mapped properties and associations...


@Entity
public class BB1 extends BB

    // other stuff, not necessarily mapped...


@Entity
public class BB2 extends BB

    // other stuff, not necessarily mapped...


@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
public class CC extends A

    // other mapped properties and associations...


@Entity
public class CC1 extends CC

    // other stuff, not necessarily mapped...


...

我对这个映射的期望是正好有 3 个表:ABBCCBBCC 都应该有一个名为 DTYPE 的默认鉴别器列。它们还应提供所有映射属性所需的所有列以及它们各自子类的关联。

相反,类层次结构似乎始终使用 table-per-subclass 继承策略。 IE。我为上面提到的每个实体都有一个自己的表。我想避免这种情况,因为类层次结构的叶子是非常轻量级的,并且为它们每个单独的表似乎有点过头了!


我是否忽略了什么?任何建议都非常感谢!我很乐意提供更多信息...

【问题讨论】:

这在 Hibernate 3.6.3 上对我不起作用 @Meeque 如果 A 是“TABLE_PER_CLASS”并且您希望 C 类(以及它下面的层次结构)相同,并且希望 SINGLE_TABLE 仅用于植根于 BB 类的层次结构,该怎么办?在您下面的解决方案中,您已经反转了解决方案的问题,您认为我提到的变化可以解决吗 【参考方案1】:

根据 Hibernate 参考文档,在使用 Hibernate 的 XML 元数据时应该可以混合不同的继承映射策略 (...)

实际上,它并不真正受支持,他们在“作弊”使用辅助表从文档示例中的单表策略切换。引用 Java Persistence with Hibernate

您可以映射整个继承 嵌套层次结构 <union-subclass>, <sub- class>, 和<joined-subclass> 映射 元素。你不能混合它们——因为 例如,从一个 每类表的层次结构 归一化的鉴别器 每个子类的表策略。 一次 你已经做出了一个决定 继承策略,你必须 坚持下去

然而,这并不完全正确。 通过一些 Hibernate 技巧,您可以 切换一个映射策略 特定的子类。例如,你 可以将类层次结构映射到单个 表,但对于特定的子类, 切换到一个单独的表 外键映射策略,就像 每个子类都有表。这是 可以使用 <join> 映射 元素:

<hibernate-mapping>
  <class name="BillingDetails"
      table="BILLING_DETAILS">

    <id>...</id>

    <discriminator
        column="BILLING_DETAILS_TYPE"
        type="string"/>
    ...
    <subclass
        name="CreditCard"
        discriminator-value="CC">
      <join table="CREDIT_CARD">
        <key column="CREDIT_CARD_ID"/>

        <property name="number" column="CC_NUMBER"/>
        <property name="expMonth" column="CC_EXP_MONTH"/>
        <property name="expYear" column="CC_EXP_YEAR"/>
        ...
      </join>
    </subclass>

    <subclass
        name="BankAccount"
        discriminator-value="BA">
      <property name=account" column="BA_ACCOUNT"/>
      ...
    </subclass>
  ...
  </class>
</hibernate-mapping>

你也可以通过注解达到同样的效果:

Java Persistence 还支持这种带有注释的混合继承映射策略。像以前一样,用InheritanceType.SINGLE_TABLE 映射超类BillingDetails。现在将要从单个表中拆分出来的子类映射到辅助表。

@Entity
@DiscriminatorValue("CC")
@SecondaryTable(
    name = "CREDIT_CARD",
    pkJoinColumns = @PrimaryKeyJoinColumn(name = "CREDIT_CARD_ID")
)
public class CreditCard extends BillingDetails 
    @Column(table = "CREDIT_CARD",
        name = "CC_NUMBER",
        nullable = false)
    private String number;
    ...

我没有对此进行测试,但您可以尝试:

使用 SINGLE_TABLE 策略映射 A 使用@SecondaryTable 注释映射BB、CC 等。

这个我没测试过,不知道BB1、BB2会不会好用。

参考

Java 持久性与 Hibernate 5.1.5 混合继承策略 (p207-p210)

【讨论】:

感谢您的快速回复,帕斯卡。我不得不说,建议的方法似乎很冗长。特别是,我必须为我在子类中映射的每个属性指定所需的表。否则它将被映射到层次结构的单个表的列,对吗?好吧,我明天试一试,我会回来报告的...... @Meeque 是的,它很冗长。但这是你唯一的选择,所以你别无选择。 嗯,这个建议其实效果很好:)但是,还是感觉不太自然,还有一些缺点。 谢谢帕斯卡。虽然这个答案是 7 年前的,但我今天用 Hibernate 5.2.10.Final 尝试了这个解决方案,它仍然有效。【参考方案2】:

为了清楚起见,这里是 Pascal 的解决方案应用于我的问题中的示例代码:

@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "entityType", 
        discriminatorType = DiscriminatorType.STRING )
public abstract class A implements Serializable

    @Id
    private String id;

    // other mapped properties...


@Entity
@SecondaryTable( name = "BB" )
public class BB extends A

    @Basic( optional = false)
    @Column( table = "BB" )
    private String property1;

    // other mapped properties and associations...


@Entity
public class BB1 extends BB

    // other stuff, not necessarily mapped...


@Entity
public class BB2 extends BB

    // other stuff, not necessarily mapped...


@Entity
@SecondaryTable( name = "CC" )
public class CC extends A

    @ManyToOne( optional = false)
    @JoinColumn( table = "CC" )
    private SomeEntity association1;

    // other mapped properties and associations...


@Entity
public class CC1 extends CC

    // other stuff, not necessarily mapped...


...

我已经成功地将这种方法应用于我的问题,我会暂时坚持下去。但是我仍然看到以下缺点:

鉴别器列位于层次结构的主表中,即根实体表A。就我而言,在辅助表BBCC 中有鉴别器列就足够了。

任何时候将属性和关联添加到BBCC 的子类,他/她必须指定它们应该映射到相应的辅助表。如果有办法将其设为默认值,那就太好了。

【讨论】:

谢谢,这救了我的命。在我们的例子中,我们在上面的BBCC 中也有@Embeddables,所以我们必须进入@Embeddable 并为每个@Column 设置表属性以匹配辅助表.我想如果我们需要为@EmbeddablesBBCC 提供不同的表,我们将需要使用@AttributeOverrides,但在这一点上有点幸运。

以上是关于如何将继承策略与 JPA 注释和 Hibernate 混合使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何将@UniqueConstraint 与单表继承(JPA)一起使用?

JPA继承方式

JPA实体继承实体的映射策略

JPA实体继承实体的映射策略

在 Kotlin 中使用 Jpa 注释从基类继承父属性

JPA:如何将@NamedStoredProcedureQuery 与@NamedQuery 结合起来执行周边搜索?