1. Overview
Relational databases don’t have a straightforward way to map class hierarchies onto database tables.
To address this, the JPA specification provides several strategies:
- MappedSuperclass?– the parent classes, can’t be entities
- Single Table – The entities from different classes with a common ancestor are placed in a single table.
- Joined Table – Each class has its table, and querying a subclass entity requires joining the tables.
- Table per Class – All the properties of a class are in its table, so no join is required.
Each strategy results in a different database structure.
Entity inheritance means that we can use polymorphic queries for retrieving all the subclass entities when querying for a superclass.
Since Hibernate is a JPA implementation, it contains all of the above as well as a few Hibernate-specific features related to inheritance.
2.?MappedSuperclass
Using the?MappedSuperclass?strategy, inheritance is only evident in the class but not the entity model.
Let’s start by creating a?Person?class that will represent a parent class:
@MappedSuperclass
public class Person {@Idprivate long personId;private String name;// constructor, getters, setters
}
Notice that this class no longer has an?@Entity?annotation, as it won’t be persisted in the database by itself.
Next, let’s add an?Employee?subclass:
@Entity
public class MyEmployee extends Person {private String company;// constructor, getters, setters
}
In the database, this will correspond to one?MyEmployee?table with three columns for the declared and inherited fields of the subclass.
If we’re using this strategy, ancestors cannot contain associations with other entities.
3. Single Table
The Single Table strategy creates one table for each class hierarchy.?JPA also chooses this strategy by default if we don’t specify one explicitly.
We can define the strategy we want to use by adding the?@Inheritance?annotation to the superclass:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class MyProduct {@Idprivate long productId;private String name;// constructor, getters, setters
}
The identifier of the entities is also defined in the superclass.
Then we can add the subclass entities:
@Entity
public class Book extends MyProduct {private String author;
}
@Entity
public class Pen extends MyProduct {private String color;
}
3.1. Discriminator Values
Since the records for all entities will be in the same table,?Hibernate needs a way to differentiate between them.
By default, this is done through a discriminator column called?DTYPE?that has the name of the entity as a value.
To customize the discriminator column, we can use the?@DiscriminatorColumn?annotation:
@Entity(name="products")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="product_type", discriminatorType = DiscriminatorType.INTEGER)
public class MyProduct {// ...
}
Here we’ve chosen to differentiate?MyProduct?subclass entities by an?integer?column called?product_type.
Next, we need to tell Hibernate what value each subclass record will have for the?product_type?column:
@Entity
@DiscriminatorValue("1")
public class Book extends MyProduct {// ...
}
@Entity
@DiscriminatorValue("2")
public class Pen extends MyProduct {// ...
}
Hibernate adds two other predefined values that the annotation can take —?null?and?not null:
- @DiscriminatorValue(“null”)?means that any row without a discriminator value will be mapped to the entity class with this annotation; this can be applied to the root class of the hierarchy.
- @DiscriminatorValue(“not null”)?– Any row with a discriminator value not matching any of the ones associated with entity definitions will be mapped to the class with this annotation.
Instead of a column, we can also use the Hibernate-specific?@DiscriminatorFormula?annotation to determine the differentiating values:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when author is not null then 1 else 2 end")
public class MyProduct { ... }
This strategy has the advantage of polymorphic query performance since only one table needs to be accessed when querying parent entities.
On the other hand, this also means that?we can no longer use?NOT NULL?constraints on subclass?entity properties.
4. Joined Table
Using this strategy, each class in the hierarchy is mapped to its table.?The only column that repeatedly appears in all the tables is the identifier, which will be used for joining them when needed.
Let’s create a superclass that uses this strategy:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {@Idprivate long animalId;private String species;// constructor, getters, setters
}
Then we can simply define a subclass:
@Entity
public class Pet extends Animal {private String name;// constructor, getters, setters
}
Both tables will have an?animalId?identifier column.
The primary key of the?Pet?entity also has a foreign key constraint to the primary key of its parent entity.
To customize this column, we can add the?@PrimaryKeyJoinColumn?annotation: