Inheritance is one of the key concepts in Java, and it’s used in most domain models. That often becomes an issue, if you try to map these models to a relational database. SQL doesn’t support this kind of relationship and Hibernate, or any other JPA implementation has to map it to a supported concept.
You can choose between 4 strategies which map the inheritance structure of your domain model to different table structures. Each of these strategies has its advantages and disadvantages. It’s, therefore, important to understand the different concepts and to choose the one that fits best.
I will use the same simple domain model in all of the examples to show you the different inheritance strategies. It consists of an author who has written different kinds of publications. A publication can either be a book or a blog post. Both of them share most of their attributes, like the id, a title, and a publishing date. In addition to the shared attributes, the book also stores the number of pages, and the blog post persists its URL.
4 Inheritance Strategies
JPA and Hibernate support 4 inheritance strategies which map the domain objects to different table structures.
The mapped superclass strategy is the simplest approach to mapping an inheritance structure to database tables. It maps each concrete class to its own table.
That allows you to share the attribute definition between multiple entities. But it also has a huge drawback. A mapped superclass is not an entity, and there is no table for it.
That means that you can’t use polymorphic queries that select all Publication entities and you also can’t define a relationship between an Author entity and all Publications. You either need to use uni-directional relationship from the Publication to the Author entity, or you have to define a relationship between an Author and each kind of Publication. In general, if you need these relationships, you should have a look at the other inheritance strategies. They are most likely a better fit for your use case.
If you just want to share state and mapping information between your entities, the mapped superclass strategy is a good fit and easy to implement. You just have to set up your inheritance structure, annotate the mapping information for all attributes and add the @MappedSuperclass annotation to your superclass. Without the @MappedSuperclass annotation, Hibernate will ignore the mapping information of your superclass.
You can see an example of such a mapping in the following code snippets. the Publication class is annotated with @MappedSuperclass and provides the shared attributes with their mapping annotations. As you can see, Publication has no @Entity annotation and will not be managed by the persistence provider.
The subclasses Book and BlogPost extend the Publication class and add their specific attributes with their mapping annotations. Both classes are also annotated with @Entity and will be managed by the persistence provider.
As I explained at the beginning of this section, you can’t use the inheritance structure for polymorphic queries or to define relationships. But you can, of course, query the entites as any other entity.
The Book entity and all its attributes are mapped to the book table. This makes the generated query simple and efficient. It just has to select all columns of the book table.
Table per Class
The table per class strategy is similar to the mapped superclass strategy. The main difference is that the superclass is now also an entity. Each of the concrete classes gets still mapped to its own database table. This mapping allows you to use polymorphic queries and to define relationships to the superclass. But the table structure adds a lot of complexity to polymorphic queries, and you should, therefore, avoid them. The definition of the superclass with the table per class strategy looks similar to any other entity definition. You annotate the class with @Entity and add your mapping annotations to the attributes. The only difference is the additional @Inheritance annotation which you have to add to the class to define the inheritance strategy. In this case, it’s the InheritanceType.TABLE_PER_CLASS.
The definitions of the Book and BlogPost entities are identical to the previously discussed mapped superclass strategy. You just have to extend the Publication class, add the @Entity annotation and add the class specific attributes with their mapping annotations.
The table per class strategy maps each entity to its own table which contains a column for each entity attribute. That makes the query for a specific entity class easy and efficient.
The superclass is now also an entity and you can, therefore, use it to define a relationship between the Author and the Publication entity. This allows you to call the getPublications() method to get all Publications written by that Author. Hibernate will map each Publication to its specific subclass.
The Java code looks easy and comfortable to use. But if you have a look at the generated SQL statement, you recognize that the table model makes the required query quite complicated.
Hibernate has to join the author table with the result of a subselect which uses a union to get all matching records from the book and blogpost tables. Depending on the amounts of records in both tables, this query might become a performance issue. And it gets even worse if you add more subclasses to the inheritance structure. You should, therefore, try to avoid these kinds of queries or choose a different inheritance strategy.
The single table strategy maps all entities of the inheritance structure to the same database table. This approach makes polymorphic queries very efficient and provides the best performance.
But it also has some drawbacks. The attributes of all entities are mapped to the same database table. Each record uses only a subset of the available columns and sets the rest of them to null. You can, therefore, not use not null constraints on any column that isn’t mapped to all entities. That can create data integrity issues, and your database administrator might not be too happy about it.
When you persist all entities in the same table, Hibernate needs a way to determine the entity class each record represents. This is information is stored in a discriminator column which is not an entity attribute. You can either define the column name with a @DiscriminatorColumn annotation on the superclass or Hibernate will use DTYPE as its default name.
The definition of the subclasses is again similar to the previous examples. But this time, you should also provide a @DiscriminatorValue annotation. It specifies the discriminator value for this specific entity class so that your persistence provider can map each database record to a concrete entity class.
The @DiscriminatorValue annotation is optional if you use Hibernate. If you don’t provide a discriminator value, Hibernate will use the simple entity name by default. But this default handling isn’t defined by the JPA specification, and you shouldn’t rely on it.
As I explained at the beginning of this section, the single table strategy allows easy and efficient data access. All attributes of each entity are stored in one table, and the query doesn’t require any join statements. The only thing that Hibernate needs to add to the SQL query to fetch a particular entity class is a comparison of the discriminator value. In this example, it’s a simple expression that checks that the column publication_type contains the value ‘Book‘.
The previously discussed inheritance strategies had their issues with polymorphic queries. They were either not supported or required complex union and join operations. That’s not the case if you use the single table strategy. All entities of the inheritance hierarchy are mapped to the same table and can be selected with a simple query. The following code and log snippets show an example for such a query. As you can see in the log messages, Hibernate selects all columns, including the discriminator column publication_type, from the publication table. It then uses the discriminator value to select the right entity class and to map the database record. This query is much easier than the one created by the table per class strategy, and you don’t need to worry about performance problems.
The joined table approach maps each class of the inheritance hierarchy to its own database table. This sounds similar to the table per class strategy. But this time, also the abstract superclass Publication gets mapped to a database table. This table contains columns for all shared entity attributes. The tables of the subclasses are much smaller than in the table per class strategy. They hold only the columns specific for the mapped entity class and a primary key with the same value as the record in the table of the superclass. Each query of a subclass requires a join of the 2 tables to select the columns of all entity attributes. That increases the complexity of each query, but it also allows you to use not null constraints on subclass attributes and to ensure data integrity. The definition of the superclass Publication is similar to the previous examples. The only difference is the value of the inheritance strategy which is InheritanceType.JOINED.
The definition of the subclasses doesn’t require any additional annotations. They just extend the superclass, provide an @Entity annotation and define the mapping of their specific attributes.
As I already explained, the columns mapped by each subclass are stored in 2 different database tables. The publication table contains all columns mapped by the superclass Publication and the book table all columns mapped by the Book entity. Hibernate needs to join these 2 tables by their primary keys to select all attributes of the Book entity. This is an overhead that makes these queries slightly slower than the simpler queries generated for the single table strategy.
Hibernate has to use a similar approach for polymorphic queries. It has to left join the publication table with all tables of the subclasses, to get all Pubications of an Author.
Choosing a Strategy
Choosing the right inheritance strategy is not an easy task. As so often, you have to decide which advantages you need and which drawback you can accept for your application. Here are a few recommendations:
- If you require the best performance and need to use polymorphic queries and relationships, you should choose the single table strategy. But be aware, that you can’t use not null constraints on subclass attributes which increase the risk of data inconsistencies.
- If data consistency is more important than performance and you need polymorphic queries and relationships, the joined strategy is probably your best option.
- If you don’t need polymorphic queries or relationships, the table per class strategy is most likely the best fit. It allows you to use constraints to ensure data consistency and provides an option of polymorphic queries. But keep in mind, that polymorphic queries are very complex for this table structure and that you should avoid them.