1. 引言
继承是Java的核心概念之一,因此大多数领域模型都会使用它。但可惜的是,关系型数据库并不直接支持继承,你需要找到方法将继承层次结构映射到关系表模型中。
JPA和Hibernate支持多种策略,可将继承层次结构映射到不同的表模型。本文重点介绍单表策略(SingleTable)——它将继承层次结构中的所有类映射到同一张数据库表。
📚 更多继承映射策略详见我的《Hibernate Tips》书籍,其中包含70+个即用型解决方案,涵盖基础/高级映射、日志、Java 8支持、缓存以及静态/动态查询等主题。
2. Hibernate技巧:将继承层次结构映射到单表
2.1 问题场景
数据库中存在一张表,需要将其映射到实体的继承层次结构。如何定义这种映射?
2.2 解决方案
JPA和Hibernate提供多种继承策略,可将实体映射到不同表结构。单表策略(SingleTable)就是其中之一,它将整个继承层次结构映射到单张数据库表。
先看实体模型:Author(作者)可创作多种Publication(出版物),如Book(书籍)和BlogPost(博客)。Publication是Book和BlogPost的超类。
单表策略将这三个实体映射到publication表:
实现步骤
超类配置
在超类添加@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>(); ... }
子类配置
子类需继承超类并添加@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将继承层次结构映射到单表非常简单,只需:
- 在超类添加
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- 在子类明确指定
@DiscriminatorValue("Book")
💡 更多即用型解决方案请参考《Hibernate Tips:70+个常见Hibernate问题解决方案》,涵盖基础/高级映射、日志、Java 8支持、缓存及静态/动态查询等主题。