1. 引言

继承是Java的核心概念之一,因此大多数领域模型都会使用它。但可惜的是,关系型数据库并不直接支持继承,你需要找到方法将继承层次结构映射到关系表模型中。

JPA和Hibernate支持多种策略,可将继承层次结构映射到不同的表模型。本文重点介绍单表策略(SingleTable)——它将继承层次结构中的所有类映射到同一张数据库表。

📚 更多继承映射策略详见我的《Hibernate Tips》书籍,其中包含70+个即用型解决方案,涵盖基础/高级映射、日志、Java 8支持、缓存以及静态/动态查询等主题。


2. Hibernate技巧:将继承层次结构映射到单表

2.1 问题场景

数据库中存在一张表,需要将其映射到实体的继承层次结构。如何定义这种映射?

2.2 解决方案

JPA和Hibernate提供多种继承策略,可将实体映射到不同表结构。单表策略(SingleTable)就是其中之一,它将整个继承层次结构映射到单张数据库表。

先看实体模型:Author(作者)可创作多种Publication(出版物),如Book(书籍)和BlogPost(博客)。PublicationBookBlogPost的超类。

继承实体模型

单表策略将这三个实体映射到publication表:

单表继承映射

实现步骤

  1. 超类配置
    在超类添加@Inheritance(strategy = InheritanceType.SINGLE_TABLE)注解。可通过@DiscriminatorColumn自定义鉴别器列名(若省略,Hibernate默认使用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>();
    
        ...
    }
    
  2. 子类配置
    子类需继承超类并添加@Entity注解。强烈建议使用@DiscriminatorValue指定鉴别值(否则Hibernate将使用实体名作为默认值,可能导致跨JPA实现兼容性问题)。

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

查询优势

单表策略在以下场景中表现高效:

  • ✅ 查询特定实体
  • ✅ 执行多态查询
  • ✅ 遍历多态关联
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=?

2.3 源码下载

可执行测试案例项目请见《Hibernate Tips》书籍配套资源。

2.4 延伸阅读

继承层次结构也可映射到多张数据库表,详见《如何将继承层次结构映射到多表》章节。


3. 总结

通过本文可看到:使用JPA/Hibernate将继承层次结构映射到单表非常简单,只需:

  1. 在超类添加@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  2. 在子类明确指定@DiscriminatorValue("Book")

💡 更多即用型解决方案请参考《Hibernate Tips:70+个常见Hibernate问题解决方案》,涵盖基础/高级映射、日志、Java 8支持、缓存及静态/动态查询等主题。


原始标题:How to Map an Inheritance Hierarchy to One Table