Contents

Hibernate 技巧——如何将继承层次结构映射到一个表

1. 简介

继承是 Java 中的关键概念之一。因此,大多数领域模型都使用它也就不足为奇了。但不幸的是,这个概念在关系数据库中并不存在,您需要找到一种方法将继承层次结构映射到关系表模型。

JPA 和 Hibernate 支持不同的策略,将继承层次结构映射到各种表模型。让我们看一下我的新书Hibernate Tips – 70 多种常见Hibernate 问题的解决方案的一章,其中我解释了SingleTable策略。它将继承层次结构的所有类映射到同一个数据库表。

2. Hibernate 技巧——如何将继承层次结构映射到一个表

2.1. 问题

我的数据库包含一个表,我想将其映射到实体的继承层次结构。如何定义这样的映射?

2.2. 解决方案

JPA 和 Hibernate 支持不同的继承策略,允许您将实体映射到不同的表结构。SingleTable策略就是其中之一,它将实体的继承层次结构映射到单个数据库表。

在我解释SingleTable策略的细节之前,我们先看一下实体模型。Authors 可以编写不同类型的Publication ,例如BookBlogPostPublication类是BookBlogPost类的超类。

/uploads/hibernate_tips_how_to_map_an_inheritance_hierarchy_to_one_table/1.png

SingleTable策略将继承层次结构的三个实体映射到*publication *表。 /uploads/hibernate_tips_how_to_map_an_inheritance_hierarchy_to_one_table/3.png

如果要使用此继承策略,则需要使用*@Inheritance对超类进行注解,并提供InheritanceType.SINGLE_TABLE作为strategy *属性的值。

您还可以使用*@DiscriminatorColumn对超类进行注解,以定义鉴别器值的名称。Hibernate 使用这个值来确定它必须将数据库记录映射到的实体。如果您没有定义鉴别器列,就像我在下面的代码片段中所做的那样,Hibernate 和所有其他 JPA 实现都使用列DTYPE*。

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Publication {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
    @Version
    private int version;
    private String title;
    private LocalDate publishingDate;
	
    @ManyToMany
    @JoinTable(
      name="PublicationAuthor",
      joinColumns={@JoinColumn(name="publicationId", referencedColumnName="id")},
      inverseJoinColumns={@JoinColumn(name="authorId", referencedColumnName="id")})
    private Set<Author> authors = new HashSet<Author>();
    ...
}

子类需要扩展超类,并且您需要使用*@Entity*对它们进行注解。

JPA 规范还建议使用*@DiscriminatorValue*对其进行注解,以定义此实体类的鉴别器值。如果您不提供此注解,您的 JPA 实现会生成一个鉴别器值。

但是 JPA 规范没有定义如何生成鉴别器值,并且您的应用程序可能无法移植到其他 JPA 实现。Hibernate 使用简单的实体名称作为鉴别器。

@Entity
@DiscriminatorValue("Book")
public class Book extends Publication {
    private int numPages;
    ...
}

如果要选择特定实体、执行多态查询或遍历多态关联,SingleTable策略不需要 Hibernate 生成任何复杂查询。

Author a = em.find(Author.class, 1L);
List<Publication> publications = a.getPublications();

所有实体都存储在同一个表中,Hibernate 可以从那里选择它们而无需额外的JOIN子句。

15:41:28,379 DEBUG [org.hibernate.SQL] - 
    select
        author0_.id as id1_0_0_,
        author0_.firstName as firstNam2_0_0_,
        author0_.lastName as lastName3_0_0_,
        author0_.version as version4_0_0_ 
    from
        Author author0_ 
    where
        author0_.id=?
15:41:28,384 DEBUG [org.hibernate.SQL] - 
    select
        publicatio0_.authorId as authorId2_2_0_,
        publicatio0_.publicationId as publicat1_2_0_,
        publicatio1_.id as id2_1_1_,
        publicatio1_.publishingDate as publishi3_1_1_,
        publicatio1_.title as title4_1_1_,
        publicatio1_.version as version5_1_1_,
        publicatio1_.numPages as numPages6_1_1_,
        publicatio1_.url as url7_1_1_,
        publicatio1_.DTYPE as DTYPE1_1_1_ 
    from
        PublicationAuthor publicatio0_ 
    inner join
        Publication publicatio1_ 
            on publicatio0_.publicationId=publicatio1_.id 
    where
        publicatio0_.authorId=?