1. 概述
JPA是Java应用开发中的利器。Java中的继承概念我们都很熟悉,但当涉及JPA实体继承时,JPA提供了多种处理策略:单表存储、连接表存储或每个子类独立表存储。本文聚焦单表存储策略,探讨如何高效查询继承体系中的子类型数据。
2. 单表继承子类型
在JPA中,通过@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
配置单表继承。这意味着整个继承体系的所有实体数据都存储在同一张表中,并通过鉴别器列(discriminator column)区分不同子类型。我们可以直接通过JPA仓库查询特定子类型。
以Employee
基类为例:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="type")
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ...其他字段、getter和setter
}
创建子类PermanentEmployee
和ContractEmployee
:
@Entity
@DiscriminatorValue("PERM")
public class PermanentEmployee extends Employee {
private int employeeId;
// ...其他字段、getter和setter
}
@Entity
@DiscriminatorValue("CNTR")
public class ContractEmployee extends Employee {
private int contractPeriod;
// ...其他字段、getter和setter
}
关键注解解析:
@Inheritance
:在基类Employee
定义继承策略,SINGLE_TABLE
指示Hibernate将所有子类数据存入单表@DiscriminatorColumn
:定义鉴别器列(本例中列名为type
),用于区分记录所属子类型@DiscriminatorValue
:在子类指定鉴别器值(如PermanentEmployee
用"PERM"
,ContractEmployee
用"CNTR"
)
⚠️ 踩坑提示:鉴别器列名和值需保持唯一性,否则会导致类型识别混乱。
3. 使用JPA仓库查询
3.1. 仓库类
为继承体系中的基类和子类创建仓库接口,只需继承JpaRepository
:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
继承JpaRepository
后自动获得save
、findAll
、findById
、delete
等基础方法。
子类仓库接口同理:
public interface PermanentEmployeeRepository extends JpaRepository<PermanentEmployee, Long> {
}
public interface ContractEmployeeRepository extends JpaRepository<ContractEmployee, Long> {
}
3.2. 持久化配置
在配置类中声明EntityManagerFactory
并配置Hibernate适配器:
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.baeldung.jpa.subtypes.entity");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
}
LocalContainerEntityManagerFactoryBean
是Spring提供的工厂Bean,用于配置JPA的EntityManagerFactory
,包括数据源、实体包扫描等。
3.3. 使用JPA仓库查询
在服务类中注入仓库实例:
@Autowired
EmployeeRepository empRepository;
@Autowired
PermanentEmployeeRepository permEmpRepository;
@Autowired
ContractEmployeeRepository contrEmpRepository;
添加测试数据:
PermanentEmployee perm1 = new PermanentEmployee(10, "John", 48);
permEmpRepository.save(perm1);
ContractEmployee contr1 = new ContractEmployee(180, "Mitchell", 23);
contrEmpRepository.save(contr1);
根据单表配置,所有数据存入Employee
表。数据结构示例:
ID | NAME | AGE | EMPLOYEEID | CONTRACTPERIOD | TYPE |
---|---|---|---|---|---|
1 | John | 48 | 10 | NULL | PERM |
2 | Mitchell | 23 | NULL | 180 | CNTR |
✅ 关键点:子类特有字段在另一子类记录中为NULL,TYPE
列明确标识记录类型。
查询操作:
- 获取所有员工记录:
List<Employee> empList = empRepository.findAll();
- 仅获取永久员工:
List<PermanentEmployee> perEmpList = permEmpRepository.findAll();
- 仅获取合同员工:
List<ContractEmployee> contrList = contrEmpRepository.findAll();
3.4. 使用自定义查询
通过自定义JPA查询基于鉴别器列过滤子类型数据:
@Query("SELECT e FROM Employee e WHERE type(e) = 'PERM' AND e.employeeId < :idlimit "
+ "AND e.name LIKE :prefix% ")
List<PermanentEmployee> filterPermanent(@Param("idlimit") int idlimit, @Param("prefix") String prefix);
@Query("SELECT e FROM Employee e WHERE type(e) = 'CNTR' AND e.contractPeriod < :period "
+ "AND e.name LIKE :prefix% ")
List<ContractEmployee> filterContract(@Param("period") int period, @Param("prefix") String prefix);
✅ 优势:在基类仓库EmployeeRepository
中使用自定义查询,单仓库即可处理所有子类型记录,避免维护多个仓库。
4. 结论
本文介绍了JPA单表继承策略的实现与查询方法:
- 所有子类数据存储在单表,通过鉴别器列区分类型
- 子类仓库自动过滤对应类型数据
- 基类仓库结合自定义查询可统一管理所有子类型
完整代码示例可在GitHub仓库获取。