Relational databases do not have a built-in way to handle table inheritance. To address this limitation, JPA provides a set of inheritance strategies with Hibernate that guide us towards achieving inheritance to use with our applications.
Inheritance Strategies with Hibernate
JPA offers different strategies to implement inheritance:
- MappedSuperclass: The parent class cannot be an entity.
- Single Table – Entities of different classes with a common parent are stored in a single table.
- Joined Table – Each class has its own table, and querying subclasses requires table joins.
- Table-Per-Class – All properties are stored in their respective tables, eliminating the need for joins.
Applying inheritance with Hibernate allows us to work with class polymorphism. Each of the above strategies results in a different database structure.
MappedSuperclass Strategy
With mappedSuperclass Strategy, we aim to add inherited columns to our database table. The parent class must be annotated with @MappedSuperclass. Let’s see an example:
@MappedSuperclass @Getter @Setter public class Car { @Id private Long id; private String color; }
@Getter @Setter @Entity public class Model extends Car { private String model; }
With this last class, what we have done is inherit from the Car class, thus inheriting its attributes. In the database, we will have a Model table with three columns. It is important to note that the parent class does not have the @Entity annotation.
Joined Table Strategy
In joined table strategy, inheritance is defined in the abstract parent class using the @Inheritance annotation. Unlike the previous strategy, each child class will have its own table.
Both the parent and child tables will have the ID from the parent table, meaning there will be a column referencing it. The @PrimaryKeyJoinColumn annotation can be used to customize this identifier. The field that serves as the key will be defined only in the parent class.
@Entity @Inheritance(strategy = InheritanceType.JOINED) @Getter @Setter public class Vehicle { @Id private long vehicleId; private String model; }
In the previous class, we have defined the type of strategy to be used, and the child classes will extend from it.
@Entity @Getter @Setter public class Car extends Vehicle { private String name; }
This Car class extends Vehicle, and both classes will create a table in the database. Both tables will have the vehicleId column, and the Car table will have a foreign key constraint referencing the parent table.
As mentioned earlier, we can use the PrimaryKeyJoinColumn annotation to change the name of the inherited column.
@Entity @Getter @Setter @PrimaryKeyJoinColumn(name = "carId") public class Car extends Vehicle { }
The main disadvantage of this approach, which is related to relational databases, is that it involves performing joins with the child tables, and this can significantly impact query performance when dealing with a large number of records.
Table-per-Class Strategy
In Table-per-Class Strategy, we will have a separate table in the database for each class, including the inherited ones.
The resulting schema is similar to the one used in @MappedSuperclass, with the difference that the parent class is also a table, repeating the same attributes as many times as they are inherited.
To use this strategy, it needs to be defined as follows: @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @Getter @Setter public class Vehicle { @Id private Long vehicleId; private String model; }
This strategy also uses SQL UNION. When the specific subclass is not known, the related object could be stored in any of the subclass tables, making joins across the relationship impossible. Therefore, it provides limited support for polymorphic relationships.
Single Table Strategy
In Single Table Strategy, a single table is created for the entire class hierarchy. This is the default strategy used by JPA if not specified.
The identifier is defined in the parent class, and subclasses can extend and inherit from it.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @Getter @Setter public class vehicle { @Id private Long vehicleId; private String name; }
@Entity public class Motorbike extends Vehicle { private String color; }
@Entity public class Car extends Vehicle { private String colorDoors; }
Discriminator Values
Since all records will be stored in the same tables, we need to provide a way for Hibernate to differentiate them, and this is where Discriminator Values come into play.
By default, this discrimination is usually done through a column called DTYPE, which will have the entity name as its value. However, we often want to customize this column, so we will use the annotation @DiscriminatorColumn.
@Entity(name="vehicles") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="vehicle_type", discriminatorType = DiscriminatorType.INTEGER) public class Vehicle { // ... }
With the definition of the previous class, we have chosen to differentiate based on the column “vehicle_type” instead of “DTYPE”. Now let’s tell Hibernate how to make that distinction.
@Entity @DiscriminatorValue("1") public class Motorbike extends Vehicle { private String color; }
@Entity @DiscriminatorValue("2") public class Car extends Vehicle { private String colorDoors; }
With the definition of the @DiscriminatorValue in the previous entities, we have managed to differentiate the type of object based on the “vehicle_type” column.
We are also given the possibility to use @DiscriminatorFormula instead of @DiscriminatorColumn, where instead of adding values, a formula is specified. For example:
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("case when color is not null then 1 else 2 end") public class Vehicle { ... }
With the previous approach, we have managed to store a record based on a condition. In other words, if the “color” attribute (from the Motorbike class) is not null, it will be saved with the value 1 in the “vehicle_type” column.
In this strategy, we benefit from improved performance when performing polymorphic queries since we only access one table.
Conclusion
In this article, we have explored inheritance strategies with Hibernate. When choosing any of these strategies, it is important to analyze the objective, as the performance can be significantly affected depending on the chosen strategy. If you want to further leverage Hibernate annotations, you may also be interested in soft delete with Hibernate.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!