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
}

创建子类PermanentEmployeeContractEmployee

@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后自动获得savefindAllfindByIddelete等基础方法。

子类仓库接口同理:

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仓库获取。


原始标题:Query JPA Repository With Single Table Inheritance Subtypes | Baeldung