JPA Inheritance Strategies
Object-oriented programming thrives on inheritance, allowing code reuse and hierarchical relationships between classes. But how do you model inheritance in relational databases, which lack this concept inherently? JPA bridges this gap by providing various Java inheritance strategies to map your object hierarchies to database tables effectively.
Key Inheritance Mapping Strategies in JPA
You always have to define how inheritance should be handled. JPA distinct two scenarios:
Scenario 1: Mapped Superclass
Often you want to define fields and/or behavior in a common superclass like a timestamp when the entity was created:
Unless you don’t define how this should be handled, JPA will not consider the field createdAt when persisting the Address entity.
To tell JPA to include the fields of a superclass in the mapping you have to use the MappedSuperclass annotation:
@MappedSuperclass public class BaseEntity { private Timestamp createdAt; } @Entity public class Address extends BaseEntity { }
In that case, JPA assumes that the database table Address contains a column with the name createdAt.
Scenario 2: Mapping Inheritance
Let’s assume that we have the following inheritance hierarchy:
The Employee class is abstract and has a field name, whereas FulltimeEmployee doesn’t have its own fields but ParttimeEmployee has a field that defines the percentage he works for the company.
But how can we map this with JPA? Let’s have a look at what the specification says:
http://download.oracle.com/otndocs/jcp/persistence-2_1-fr-eval-spec/index.html
2.12 Inheritance Mapping Strategies
The mapping of class hierarchies is specified through metadata.
There are three basic strategies that are used when mapping a class or class hierarchy to a relational database:
- a single table per class hierarchy
- a joined subclass strategy, in which fields that are specific to a subclass are mapped to a separate table than the fields that are common to the parent class, and a join is performed to instantiate the subclass.
- a table per concrete entity class
An implementation is required to support the single table per class hierarchy inheritance mapping strategy and the joined subclass strategy.
Support for the table per concrete class inheritance mapping strategy is optional in this
release. Applications that use this mapping strategy will not be portable.
Support for the combination of inheritance strategies within a single entity inheritance hierarchy is not required by this specification.
Metadata
To activate inheritance we have to add the Inheritance annotation to the base class Employee:
@Inheritance @Entity public abstract class Employee { @Id private Integer id; private String name; } @Entity public class FulltimeEmployee extends Employee { } @Entity public class ParttimeEmployee extends Employee { private int percentage; }
Database Representation
OK…. but what do the database tables look like?
The Inheritance annotation has an attribute strategy of type InheritanceType. There are three inheritance types as described in the spec: SINGLE_TABLE, JOINED, TABLE_PER_CLASS.
SINGLE_TABLE
The default strategy is SINGLE_TABLE and would look like this:
As you can see all attributes of the whole inheritance hierarchy are flattened to a single table. Additionally, a column dtype was added. (The column name is not defined in the specification and is, therefore, implementation-dependent.) This column indicates which class must be instantiated when loading the data from the database.
The advantage of this strategy is that it’s easy to use even if the user directly uses the database and doesn’t know about inheritance in the entity model.
But there is also a disadvantage. Have a look at the column percentage. The N indicates that this column is nullable. But wait a ParttimeEmployee must always have a percentage! That’s not possible because the other subtype of Employee FulltimeEmployee doesn’t have this field.
So if you have mandatory fields in subclasses SINGLE_TABLE is probably not the best strategy.
JOINED
The JOINED strategy will create a table for every class in the hierarchy:
This looks like our class diagram, doesn’t it? The id is inherited means that for every instance of either FulltimeEmployee or ParttimeEmployee we will have a record in the Employee table and the primary key of Employee will be the primary and the foreign key in the FulltimeEmployee or ParttimeEmployee table.
And in contrast to the SINGLE_TABLE strategy, the percentage column in the ParttimeEmployee is now not nullable!
Perfect, but is there a disadvantage? Yes, the JPA implementation could always join the tables and this could be slower than the SINGLE_TABLE strategy. But this depends on the database and the indexes that are set on the tables.
Also querying the database with plain SQL could also be a bit more complicated because you have to do the same joins that the JPA implementation would do to get all the data.
TABLE_PER_CLASS
The last strategy is called TABLE_PER_CLASS because there is only a table if the Entity class is not abstract. In our example, the table Employee is missing because the class Employee is abstract.
In the first place, this strategy looks pretty good as it also allows to have the percentage column not nullable in the database table. But the disadvantage is when you query on the superclass level Employee then it has to execute two queries or do a union of FulltimeEmployee and ParttimeEmployee which could lead to a performance issue.
Conclusion
If you are using inheritance with JPA you can choose between the mapped superclass to have all fields of the superclass in the database table of the entity or to use “real” inheritance with one of the three inheritance strategies.
In my opinion, the TABLE_PER_CLASS strategy is not useful and with the disadvantage of multiple queries or a union probably the slowest option.
Choose SINGLE_TABLE when your subclass has only a few fields that are not mandatory. Also, choose this option when you use the database not only with JPA but also with plain SQL because this has the simplest model for querying.
In all other cases go for the JOINED strategy.
Related Posts
- Hibernate Schema-based Multi-Tenancy using StatementInspector
- How to JOIN two Entities without Mapped Relationship