Inheritance Mapping Strategies in Hibernate: Single Table, Joined, and Table per Class Explained

Illustration for Inheritance Mapping Strategies in Hibernate: Single Table, Joined, and Table per Class Explained
By Last updated:

In real-world applications, we often model entities that share common attributes but also have specialized properties. For example, consider Vehicle as a parent class with subclasses like Car and Bike. Instead of duplicating columns across multiple tables, Hibernate provides inheritance mapping strategies to persist such hierarchies efficiently.

Inheritance mapping is important because:

  • It improves database normalization and avoids redundancy.
  • It allows polymorphic queries (fetching parent or child entities with ease).
  • It enhances flexibility and maintains clean object-oriented design.

Hibernate supports three main inheritance strategies:

  1. Single Table (Default)
  2. Joined Table
  3. Table per Class

Each has trade-offs in terms of performance, schema complexity, and maintainability.


Hibernate Inheritance Mapping Strategies

1. Single Table Strategy

In this approach, all classes in the hierarchy are mapped to a single table with a discriminator column to differentiate between subclasses.

Example

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "vehicle_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String brand;
}

@Entity
@DiscriminatorValue("CAR")
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
@DiscriminatorValue("BIKE")
public class Bike extends Vehicle {
    private boolean hasGear;
}

Database Schema

CREATE TABLE vehicle (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  brand VARCHAR(255),
  numberOfDoors INT,
  hasGear BOOLEAN,
  vehicle_type VARCHAR(31)
);

Pros

  • Simple schema (only one table).
  • Polymorphic queries are efficient.

Cons

  • Many null values for unused columns.
  • Schema may become very wide for large hierarchies.

2. Joined Table Strategy

Each class in the hierarchy is mapped to its own table. Subclass tables reference the parent table with a foreign key.

Example

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String brand;
}

@Entity
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
public class Bike extends Vehicle {
    private boolean hasGear;
}

Database Schema

CREATE TABLE vehicle (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  brand VARCHAR(255)
);

CREATE TABLE car (
  id BIGINT PRIMARY KEY,
  numberOfDoors INT,
  FOREIGN KEY (id) REFERENCES vehicle(id)
);

CREATE TABLE bike (
  id BIGINT PRIMARY KEY,
  hasGear BOOLEAN,
  FOREIGN KEY (id) REFERENCES vehicle(id)
);

Pros

  • Normalized schema.
  • No null columns.

Cons

  • Joins required for queries → performance overhead.

3. Table per Class Strategy

Each subclass has its own table with all parent fields duplicated. No joins are required.

Example

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String brand;
}

@Entity
public class Car extends Vehicle {
    private int numberOfDoors;
}

@Entity
public class Bike extends Vehicle {
    private boolean hasGear;
}

Database Schema

CREATE TABLE car (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  brand VARCHAR(255),
  numberOfDoors INT
);

CREATE TABLE bike (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  brand VARCHAR(255),
  hasGear BOOLEAN
);

Pros

  • No joins required (faster queries).
  • Independent tables.

Cons

  • Data redundancy.
  • Union queries needed for polymorphic fetching.

CRUD Example with Hibernate Session

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Car car = new Car();
car.setBrand("Toyota");
car.setNumberOfDoors(4);

Bike bike = new Bike();
bike.setBrand("Hero");
bike.setHasGear(true);

session.save(car);
session.save(bike);

tx.commit();
session.close();

Performance Considerations

  • Single Table → Fast queries, but many nulls.
  • Joined Table → Clean schema, but joins reduce performance.
  • Table per Class → No joins, but redundant data.

Best Practice: Use Joined for normalized data, Single Table for small hierarchies, and Table per Class rarely (when queries are simple and performance critical).


📌 Hibernate Version Notes

Hibernate 5.x

  • Relies on javax.persistence package.
  • SessionFactory configured via XML/annotations.

Hibernate 6.x

  • Migrated to jakarta.persistence.
  • Improved HQL and Criteria API.
  • Better SQL support with database-specific optimizations.

Real-World Analogy

Think of inheritance like vehicle registration offices:

  • Single Table: One big register with all types, but many unused columns.
  • Joined Table: Separate registers, but linked with references.
  • Table per Class: Independent registers, but harder to query across all vehicles.

Conclusion & Key Takeaways

  • Hibernate supports three strategies: Single Table, Joined, Table per Class.
  • Each has trade-offs between performance, normalization, and query complexity.
  • Choose based on project requirements:
    • Small hierarchy → Single Table
    • Large hierarchy → Joined
    • Performance-focused → Table per Class

FAQs

Q1. What’s the difference between Hibernate and JPA?
Hibernate is an ORM implementation, while JPA is a specification that Hibernate implements.

Q2. How does Hibernate caching improve performance?
By storing frequently accessed entities in memory, avoiding redundant DB calls.

Q3. What are the drawbacks of eager fetching?
It loads unnecessary data, increasing memory and query cost.

Q4. How do I solve the N+1 select problem in Hibernate?
Use JOIN FETCH in HQL or adjust fetching strategies.

Q5. Can I use Hibernate without Spring?
Yes, Hibernate can be used standalone.

Q6. What’s the best strategy for inheritance mapping?
Depends on schema requirements. Joined is most commonly recommended.

Q7. How does Hibernate handle composite keys?
Using @EmbeddedId or @IdClass.

Q8. How is Hibernate 6 different from Hibernate 5?
Hibernate 6 uses Jakarta namespace and has improved SQL support.

Q9. Is Hibernate suitable for microservices?
Yes, but lightweight solutions like JOOQ may be better for some use cases.

Q10. When should I not use Hibernate?
Avoid it in cases needing fine-tuned SQL control or extremely high-performance apps.