Query JPA Single Table Inheritance
In Java Persistence API (JPA), inheritance mapping provides a way to map Java class hierarchies to database tables. Single Table Inheritance is one of the inheritance strategies where all entities in a class hierarchy are stored in a single database table. Although simple to implement, it requires a discriminator column
to distinguish between various subclasses. Let us delve into understanding JPA inheritance with single table strategy, exploring how it allows different entity types to be stored in a single database table while distinguishing them using a discriminator column.
1. Single Table Inheritance SubTypes
Single Table Inheritance is managed by using the @Inheritance
annotation with InheritanceType.SINGLE_TABLE
. In this approach, a single table is created to store all fields of both the superclass and subclasses. A discriminator column is used to identify the type of entity being stored.
2. Code Example
2.1 Setting up the Project and adding dependencies
We’ll start by creating a Spring Boot project with the help of Spring Initializr and add the necessary dependencies. In the pom.xml
file, add the following dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.2 Setting up the Mock data
We will be using a PostgreSQL database running on Docker. Once the PostgreSQL database is up and running on Docker use the below SQL commands to create the mock data.
-- Create Database CREATE DATABASE test; -- Create Employee Table with Single Table Inheritance CREATE TABLE employee ( id BIGSERIAL PRIMARY KEY, -- Unique identifier with auto-increment (BIGSERIAL) name VARCHAR(100) NOT NULL, -- Common attribute for all employees salary DOUBLE PRECISION, -- Specific to FullTimeEmployee hourly_rate DOUBLE PRECISION, -- Specific to PartTimeEmployee employee_type VARCHAR(20) NOT NULL -- Discriminator column (FULL_TIME, PART_TIME) ); -- Insert FullTimeEmployee INSERT INTO employee (name, salary, employee_type) VALUES ('John Doe', 60000, 'FULL_TIME'); INSERT INTO employee (name, salary, employee_type) VALUES ('Emily Carter', 75000, 'FULL_TIME'); -- Insert PartTimeEmployee INSERT INTO employee (name, hourly_rate, employee_type) VALUES ('Jane Smith', 15, 'PART_TIME'); INSERT INTO employee (name, hourly_rate, employee_type) VALUES ('Tom Brown', 18, 'PART_TIME');
2.3 Define the Java Subclasses
Let’s start with defining the base class: Employee
with shared properties (id
and name
) for the subclasses.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "employee_type", discriminatorType = DiscriminatoryType.STRING) public abstract class Employee { private Long id; private String name; // Constructors, Getters, and Setters }
Now, we will define two subclasses: FullTimeEmployee
and PartTimeEmployee
. Each will have its unique properties followed by the setter and getter methods. In both the below classes, employee_type
is the discriminator column, which will contain FULL_TIME
for full-time employees and PART_TIME
for part-time employees.
2.3.1 FullTimeEmployee Model class
@Entity @DiscriminatorValue("FULL_TIME") public class FullTimeEmployee extends Employee { private double salary; // Constructors, Getters, and Setters }
The given code defines a subclass of Employee
called FullTimeEmployee
, which is an entity in a Java Persistence API (JPA) context.
@DiscriminatorValue("FULL_TIME")
: This annotation is used in conjunction with single table inheritance. It specifies the value that will be stored in the discriminator column of the table to distinguish between different types of employees. In this case, the discriminator value “FULL_TIME” will be used to identify instances ofFullTimeEmployee
in the database table.
2.3.2 PartTimeEmployee Model class
@Entity @DiscriminatorValue("PART_TIME") public class PartTimeEmployee extends Employee { private double hourlyRate; // Constructors, Getters, and Setters }
The provided code defines a subclass of Employee
called PartTimeEmployee
, which is a JPA entity.
@DiscriminatorValue("PART_TIME")
: This annotation is used when implementing Single Table Inheritance in JPA. It specifies that the value “PART_TIME” will be stored in the discriminator column of the database table for rows representingPartTimeEmployee
instances. This allows the system to distinguish between different types of employees, such as full-time or part-time, stored in the same table.
2.4 Querying with JPA Repository
Spring Data JPA allows us to query entities using the repository pattern. We will create a repository interface for Employee
and then demonstrate how to query for specific subtypes.
2.4.1 Define the EmployeeRepository
Create an interface EmployeeRepository
extending JpaRepository
to provide CRUD (Create, Read, Update, and Delete) operations.
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface EmployeeRepository extends JpaRepository<Employee, Long> { List<FullTimeEmployee> findBySalaryGreaterThan(double salary); List<PartTimeEmployee> findByHourlyRateLessThan(double hourlyRate); }
2.4.2 Testing Queries in Service Layer
We will add a service class, EmployeeService
, to handle the querying logic for each subtype. Here, we can inject EmployeeRepository
and use the specific queries based on the subclass.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class EmployeeService { @Autowired private EmployeeRepository employeeRepository; public List<FullTimeEmployee> getHighSalaryEmployees(double salary) { return employeeRepository.findBySalaryGreaterThan(salary); } public List<PartTimeEmployee> getLowHourlyRateEmployees(double hourlyRate) { return employeeRepository.findByHourlyRateLessThan(hourlyRate); } }
2.4.3 Controller Layer to Test API Endpoints
Now, we’ll add a controller to expose REST endpoints for these queries.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/employees") public class EmployeeController { @Autowired private EmployeeService employeeService; @GetMapping("/full-time/high-salary") public List<FullTimeEmployee> getHighSalaryEmployees(@RequestParam double salary) { return employeeService.getHighSalaryEmployees(salary); } @GetMapping("/part-time/low-hourly-rate") public List<PartTimeEmployee> getLowHourlyRateEmployees(@RequestParam double hourlyRate) { return employeeService.getLowHourlyRateEmployees(hourlyRate); } }
2.5 Setting up the application properties
Add the following code to the application.properties file.
spring.datasource.url=jdbc:postgresql://localhost:5432/test spring.datasource.username=your_username spring.datasource.password=your_password spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
2.6 Create a Main class
Add the following code to the main class that act as the entry point to the Spring boot application.
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class EmployeeApplication { public static void main(String[] args) { SpringApplication.run(EmployeeApplication.class, args); } }
3. Run the application
Run the code by running the EmployeeApplication.java
and trigger the api endpoints.
-- GET /employees/full-time/high-salary?salary=50000 [ { "id": 1, "name": "John Doe", "salary": 60000.0, "employee_type": "FULL_TIME" }, ... ] -- GET /employees/part-time/low-hourly-rate?hourlyRate=20 [ { "id": 2, "name": "Jane Smith", "hourlyRate": 15.0, "employee_type": "PART_TIME" }, ... ]
4. Conclusion
Using Single Table Inheritance in JPA can simplify the database structure by storing different entity types in a single table. With Spring Data JPA, querying by specific subtypes is straightforward. This approach is efficient for handling entities with shared attributes and enables easier database management. However, when there are a lot of subtype-specific fields, other inheritance strategies might be better suited. This example provides a complete overview of implementing and querying Single Table Inheritance with JPA in a Spring Boot application.