Inheritance is a fundamental concept in object-oriented programming. In Java, classes can inherit fields and methods from their parent classes. But how do we map this inheritance hierarchy to relational databases using JPA (Java Persistence API)?
JPA provides multiple inheritance mapping strategies to represent class hierarchies in relational tables:
- Single Table Strategy (
InheritanceType.SINGLE_TABLE
) - Joined Strategy (
InheritanceType.JOINED
) - Table per Class Strategy (
InheritanceType.TABLE_PER_CLASS
)
Each strategy has trade-offs in performance, normalization, and schema design. In this tutorial, we’ll cover them with examples, SQL outputs, pros and cons, and best practices.
Setup and Configuration
persistence.xml
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
<persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
<class>com.example.Employee</class>
<class>com.example.FullTimeEmployee</class>
<class>com.example.PartTimeEmployee</class>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
<property name="jakarta.persistence.jdbc.user" value="sa"/>
<property name="jakarta.persistence.jdbc.password" value=""/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
Common Base Entity
import jakarta.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "employee_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
1. Single Table Strategy
Entities
@Entity
@DiscriminatorValue("FULL_TIME")
public class FullTimeEmployee extends Employee {
private double salary;
}
@Entity
@DiscriminatorValue("PART_TIME")
public class PartTimeEmployee extends Employee {
private double hourlyWage;
}
Generated Schema
create table employee (
id bigint generated by default as identity,
name varchar(255),
salary double,
hourlyWage double,
employee_type varchar(31),
primary key (id)
);
Pros
- Fastest queries (single table).
- Simple schema.
Cons
- Many
NULL
columns when subclasses have distinct fields. - Schema less normalized.
2. Joined Strategy
Entities
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Entity
public class FullTimeEmployee extends Employee {
private double salary;
}
@Entity
public class PartTimeEmployee extends Employee {
private double hourlyWage;
}
Generated Schema
create table employee (
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);
create table full_time_employee (
id bigint not null,
salary double,
primary key (id),
foreign key (id) references employee
);
create table part_time_employee (
id bigint not null,
hourlyWage double,
primary key (id),
foreign key (id) references employee
);
Pros
- Normalized schema.
- No null columns.
Cons
- Requires
JOIN
s in queries → slower performance.
3. Table per Class Strategy
Entities
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
@Entity
public class FullTimeEmployee extends Employee {
private double salary;
}
@Entity
public class PartTimeEmployee extends Employee {
private double hourlyWage;
}
Generated Schema
create table full_time_employee (
id bigint generated by default as identity,
name varchar(255),
salary double,
primary key (id)
);
create table part_time_employee (
id bigint generated by default as identity,
name varchar(255),
hourlyWage double,
primary key (id)
);
Pros
- Each subclass has its own table.
- No null columns, no joins.
Cons
- Duplicate columns across tables.
- UNION queries needed for polymorphic retrieval.
CRUD Operations Example
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
FullTimeEmployee fte = new FullTimeEmployee();
fte.setName("Alice");
fte.setSalary(80000);
PartTimeEmployee pte = new PartTimeEmployee();
pte.setName("Bob");
pte.setHourlyWage(30);
em.persist(fte);
em.persist(pte);
tx.commit();
Querying with JPA
JPQL
List<Employee> employees = em.createQuery("SELECT e FROM Employee e", Employee.class).getResultList();
Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
List<Employee> results = em.createQuery(cq).getResultList();
Persistence Context Considerations
- Entities from inheritance hierarchies share the same persistence context.
- Fetching strategy (
LAZY
vsEAGER
) impacts performance, especially with Joined strategy.
Real-World Use Cases
- Single Table: Small hierarchies with limited distinct fields.
- Joined: Large hierarchies where normalization matters.
- Table per Class: When each subclass is mostly independent.
Anti-Patterns and Pitfalls
- Avoid Single Table for large hierarchies → too many nulls.
- Avoid Table per Class when polymorphic queries are frequent.
- Avoid Joined when performance is critical.
Best Practices
- Choose mapping strategy based on query patterns and schema design.
- Use
JOINED
for normalized schema,SINGLE_TABLE
for performance. - Always benchmark with real data.
📌 JPA Version Notes
- JPA 2.0: Introduced Criteria API and Metamodel.
- JPA 2.1: Added entity graphs for fetching flexibility.
- Jakarta Persistence: Migrated package from
javax.persistence
tojakarta.persistence
.
Conclusion and Key Takeaways
- JPA supports three inheritance strategies:
SINGLE_TABLE
,JOINED
,TABLE_PER_CLASS
. - Each has trade-offs in performance and schema design.
- Always align strategy with real-world requirements.
By mastering inheritance mapping strategies, you’ll design cleaner, more efficient ORM-based applications.
FAQ
1. What’s the difference between JPA and Hibernate?
JPA is a specification, Hibernate is an implementation.
2. How does JPA handle the persistence context?
It manages entities and tracks changes across transactions.
3. What are the drawbacks of eager fetching in JPA?
Loads unnecessary data, causing performance issues.
4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH
or entity graphs.
5. Can I use JPA without Hibernate?
Yes, alternatives include EclipseLink and OpenJPA.
6. What’s the best strategy for inheritance mapping in JPA?JOINED
for normalization, SINGLE_TABLE
for performance.
7. How does JPA handle composite keys?
With @EmbeddedId
or @IdClass
.
8. What changes with Jakarta Persistence?
Package namespace changed to jakarta.persistence
.
9. Is JPA suitable for microservices?
Yes, with DTOs and projections for lightweight communication.
10. When should I avoid using JPA?
For heavy batch processing or NoSQL-oriented systems.