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.


Hibernate Inheritance Mapping Strategies

Choosing the Right Inheritance Mapping Strategy

Hibernate provides several strategies for mapping inheritance hierarchies in Java to relational databases. The choice of strategy significantly impacts database schema design, query performance, data integrity, and overall application complexity. Selecting the correct strategy is crucial for building efficient and maintainable applications. The main strategies are:

  • Single Table per Class Hierarchy (SINGLE_TABLE): All classes in the hierarchy are mapped to a single database table. A discriminator column distinguishes between the different subclasses.
  • Joined Table per Subclass (JOINED): Each class in the hierarchy is mapped to a separate table. The subclass tables contain only the properties specific to that subclass and a foreign key referencing the superclass table.
  • Table per Class (TABLE_PER_CLASS): Each class in the hierarchy, including abstract base classes, is mapped to its own table. Each table contains all the properties of the corresponding class, including inherited properties.

Discussion on Factors to Consider

Performance Implications

Performance is a critical factor in choosing an inheritance mapping strategy. Each strategy has its own performance characteristics:

  • SINGLE_TABLE: Offers generally good performance for simple queries involving the base class or any of its subclasses. However, the table can become very wide with many nullable columns if the subclasses have many unique attributes. Queries that only need properties of a specific subclass might still retrieve all columns, leading to unnecessary overhead. Indexing can become complex due to the nullable columns and discriminator.
  • JOINED: For querying all properties of a specific subclass, JOINED involves multiple joins to retrieve data from the superclass and all its parent classes. This can lead to performance issues, especially with deep inheritance hierarchies. However, it avoids wide tables and reduces the amount of data retrieved for queries that only access the base class. Updates to subclass specific fields are also very efficient as they only affect the respective subclass table.
  • TABLE_PER_CLASS: Avoids joins, which can be beneficial for performance when querying a specific subclass. However, querying the entire inheritance hierarchy requires a UNION ALL across all tables, which can be slow. Updates across the whole hierarchy would require updating potentially multiple tables.

Data Integrity

Data integrity must be carefully considered to ensure data consistency and validity.

  • SINGLE_TABLE: Simpler to enforce constraints as all data resides in a single table. Foreign key relationships are straightforward to manage within the single table. However, ensuring that non-null constraints on subclass-specific columns are respected across the entire hierarchy can be challenging without complex database triggers or application-level logic.
  • JOINED: Relies on foreign keys to maintain relationships between tables, ensuring data integrity. Nullable columns are less of a concern compared to SINGLE_TABLE. Deleting a parent record requires careful cascading strategies to avoid orphaned child records.
  • TABLE_PER_CLASS: Each table must maintain its own constraints, which can lead to redundancy and potential inconsistencies if constraints are not carefully synchronized across all tables. Referential integrity between tables is more complex. It relies on the Java code to correctly manage data integrity.

Complexity

The complexity of implementation and maintenance also influences the choice of mapping strategy.

  • SINGLE_TABLE: Relatively simple to implement and maintain from a database schema perspective. However, the application code might become more complex to handle the discriminator column and nullable fields.
  • JOINED: Requires a more complex database schema with multiple tables and foreign key relationships. Mapping this schema to Hibernate can be slightly more involved. But, the class mapping is conceptually simpler, as each class corresponds to a distinct table.
  • TABLE_PER_CLASS: The database schema is straightforward with each class having its own table. However, querying the entire hierarchy requires using UNION ALL which can be complex in some databases. The application also needs to manage potential redundancy in data.

Other Factors

Besides the above factors, consider the following:

  • Size of the inheritance hierarchy: Deeper hierarchies might benefit from JOINED, while shallow hierarchies might be suitable for SINGLE_TABLE or TABLE_PER_CLASS.
  • Frequency of queries targeting the base class versus subclasses: If the base class is queried frequently, SINGLE_TABLE or TABLE_PER_CLASS might be more efficient.
  • Mutability of the inheritance hierarchy: If the hierarchy is likely to change frequently, JOINED might offer more flexibility.
  • Database Vendor Some database vendors optimize certain types of queries differently.

Code Examples (Conceptual - Not Complete)

SINGLE_TABLE 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 int cargoCapacity;
                // ... getters and setters
            } 

JOINED 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 int cargoCapacity;
                // ... getters and setters
            } 

TABLE_PER_CLASS Example

 @Entity
            @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
            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 int cargoCapacity;
                // ... getters and setters
            }