Hibernate Inheritance Mapping

This lesson covers the different strategies for mapping inheritance hierarchies to database tables in Hibernate. We will explore strategies like Table per Class Hierarchy, Table per Subclass, and Table per Concrete Class.


Introduction to Hibernate Inheritance Mapping

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows you to create new classes based on existing classes, inheriting their properties and methods. Hibernate, an Object-Relational Mapping (ORM) framework for Java, provides mechanisms to map these inheritance hierarchies to relational databases.

Overview of Inheritance Mapping Concepts in Hibernate

Hibernate offers several strategies to represent inheritance hierarchies in a relational database:

  • Table per Class Hierarchy (Single Table): All classes in the inheritance hierarchy are mapped to a single database table. A discriminator column is used to identify the type of the object stored in each row. This is often the simplest strategy but can lead to large tables with many nullable columns.
  • Table per Subclass (Joined Table): Each subclass in the inheritance hierarchy is mapped to its own database table, which includes columns for the subclass-specific properties and a foreign key to the base class table. This strategy can result in complex joins when querying across the hierarchy.
  • Table per Concrete Class (Union Subclass): Each concrete (non-abstract) class in the inheritance hierarchy is mapped to its own independent table, containing all its own properties and the inherited properties from the superclass. There is no direct relationship or foreign key constraint between the tables. This approach eliminates joins but introduces data redundancy.

1. Table per Class Hierarchy (Single Table)

In this strategy, all classes in the hierarchy share a single table. A discriminator column distinguishes the type of each row.

Example:

 @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "vehicle_type", discriminatorType = DiscriminatorType.STRING)
    public class Vehicle {

        @Id
        @GeneratedValue
        private Long id;

        private String model;

        // 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 double loadCapacity;

        // Getters and setters
    } 

2. Table per Subclass (Joined Table)

Each subclass is mapped to its own table. The subclass table contains only the subclass-specific properties and a foreign key to the superclass table.

Example:

 @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public class Vehicle {

        @Id
        @GeneratedValue
        private Long id;

        private String model;

        // Getters and setters
    }

    @Entity
    public class Car extends Vehicle {

        private int numberOfDoors;

        // Getters and setters
    }

    @Entity
    public class Truck extends Vehicle {

        private double loadCapacity;

        // Getters and setters
    } 

3. Table per Concrete Class (Union Subclass)

Each concrete class is mapped to its own table, containing all properties (both inherited and subclass-specific). There are no foreign keys linking these tables.

Example:

 @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public abstract class Vehicle {

        @Id
        @GeneratedValue
        private Long id;

        private String model;

        // Getters and setters
    }

    @Entity
    public class Car extends Vehicle {

        private int numberOfDoors;

        // Getters and setters
    }

    @Entity
    public class Truck extends Vehicle {

        private double loadCapacity;

        // Getters and setters
    } 

Challenges of Representing Inheritance in a Relational Database

Representing inheritance in a relational database introduces several challenges:

  • Data Integrity: Ensuring data consistency across related tables, especially with joined table strategies, can be complex.
  • Query Complexity: Retrieving data from multiple tables (especially with joined tables) can lead to complex and potentially slow SQL queries. Choosing the right strategy is crucial for performance.
  • Data Redundancy: The "Table per Concrete Class" strategy avoids joins but introduces data redundancy, which can impact storage space and data consistency.
  • Null Values: The "Table per Class Hierarchy" strategy can result in many nullable columns in the single table, potentially making the table larger and impacting performance.
  • Schema Evolution: Changes to the inheritance hierarchy (e.g., adding a new subclass) can require schema modifications, which can be disruptive. Careful planning is essential.
  • Choosing the Right Strategy: Selecting the appropriate inheritance mapping strategy depends on the specific requirements of the application, including the complexity of the inheritance hierarchy, the frequency of queries across the hierarchy, and performance considerations.

Each inheritance mapping strategy has its own trade-offs, and the best choice depends on the specific needs of your application. Consider the complexity of your inheritance hierarchy, the frequency of queries, and performance requirements when selecting a strategy.