Inheritance Strategies with JPA and Hibernate

 Inheritance is one of the key concepts in Java. So, it’s no surprise that most developers want to use it in their domain model. Unfortunately, the relational table model doesn’t know this concept. So, Hibernate, or any other JPA implementation, has to apply a mapping strategy to map your inheritance hierarchy to one or more database tables.There are three primary inheritance strategies you can use: SINGLE_TABLE, TABLE_PER_CLASS, and JOINED. Let’s explore each with examples:


1. SINGLE_TABLE Strategy

All classes in the hierarchy are mapped to a single database table. This is the default inheritance strategy in JPA.

Characteristics:

  • Simplest and most efficient strategy.
  • A discriminator column is used to differentiate between subclasses.
  • Schema evolution is easier, but the table can become large and sparse due to nullable columns.

Example:

Entity Classes

import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING) public abstract class Vehicle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Getters and setters } @Entity @DiscriminatorValue("Car") public class Car extends Vehicle { private int numberOfDoors; // Getters and setters } @Entity @DiscriminatorValue("Truck") public class Truck extends Vehicle { private int payloadCapacity; // Getters and setters }

Generated Table:

idnametypenumberOfDoorspayloadCapacity
1HondaCar4NULL
2VolvoTruckNULL10000

2. TABLE_PER_CLASS Strategy

Each entity in the hierarchy is mapped to its own table, containing all fields of the entity and its superclass.

Characteristics:

  • No discriminator column.
  • Polymorphic queries require UNION operations, which can impact performance.
  • Tables do not have nullable columns.

Example:

Entity Classes

import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Vehicle { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; // Getters and setters } @Entity public class Car extends Vehicle { private int numberOfDoors; // Getters and setters } @Entity public class Truck extends Vehicle { private int payloadCapacity; // Getters and setters }

Generated Tables:

Car Table

idnamenumberOfDoors
1Honda4

Truck Table

idnamepayloadCapacity
2Volvo10000

3. JOINED Strategy

Each class in the hierarchy has its own table, and tables are linked via foreign keys.

Characteristics:

  • Normalized schema, avoiding data duplication.
  • Efficient storage, but joins can impact query performance.
  • Ideal when dealing with large hierarchies.

Example:

Entity Classes

import jakarta.persistence.*; @Entity @Inheritance(strategy = InheritanceType.JOINED) public abstract class Vehicle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Getters and setters } @Entity public class Car extends Vehicle { private int numberOfDoors; // Getters and setters } @Entity public class Truck extends Vehicle { private int payloadCapacity; // Getters and setters }

Generated Tables:

Vehicle Table

idname
1Honda
2Volvo

Car Table

idnumberOfDoors
14

Truck Table

idpayloadCapacity
210000

Comparison

StrategyAdvantagesDisadvantages
SINGLE_TABLESimple and fast queries.Table may become sparse and large.
TABLE_PER_CLASSAvoids nullable columns.Slower polymorphic queries (due to UNION).
JOINEDNormalized schema with minimal redundancy.Requires complex joins for queries.

Choosing a Strategy:

  • Use SINGLE_TABLE for simplicity and performance in smaller hierarchies.
  • Use TABLE_PER_CLASS when subclass-specific tables are needed without null fields.
  • Use JOINED for normalized design in complex hierarchies where data redundancy is a concern.

Comments