|

Inheritance Strategies with JPA and Hibernate – The Complete Guide


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Inheritance is one of the key concepts in Java. So, it’s no surprise that most developers want to use it in their domain model. Unfortunately, the relational table model doesn’t know this concept. So, Hibernate, or any other JPA implementation, has to apply a mapping strategy to map your inheritance hierarchy to one or more database tables.

You can choose between 4 strategies that 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.

Domain Model

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.

entitymodel

4 Inheritance Strategies

JPA and Hibernate support 4 inheritance strategies that map the domain objects to different table structures. Let’s take a closer look at each of them.

Mapped Superclass

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.

mappedSuperClass

That allows you to share attributes and their mapping definitions between multiple entities. But it also has a huge drawback. A mapped superclass is not an entity, and it’s not mapped to a database table.

That means that you can’t use polymorphic queries that select all Publication entities or define an association between an Author entity and all Publications. You either need to use a uni-directional association from the Publication to the Author entity or define associations between an Author and each kind of Publication. If you need such associations, you should use one of the other inheritance strategies. They are a much better fit for your use case.

If you only want to share state and mapping information between your entities, the mapped superclass strategy is a good fit and easy to implement. You set up your inheritance structure, add the required mapping annotations to each attribute, 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. I annotated the Publication class with @MappedSuperclass and defined the mapping of the shared attributes. As you can see, Publication has no @Entity annotation and the persistence provider will not manage it.

@MappedSuperclass
public abstract class Publication {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	protected Long id;

	protected String title;

	@Version
	private int version;

	private LocalDate publishingDate;

	…
}

The subclasses Book and BlogPost extend the Publication class and add their specific attributes with their mapping annotations. I annotated both classes with @Entity, and the persistence provider will manage them.

@Entity
public class Book extends Publication {

	private int pages;

	…
}
@Entity
public class BlogPost extends Publication {

	private String url;

	…
}

As I explained at the beginning of this section, you can’t perform polymorphic queries on inheritance hierarchies mapped using the @MappedSuperclass strategy. You also can’t define an association to the superclass. But you can, of course, query the entities in the same way as any other entity.

List books = em.createQuery("SELECT b FROM Book b", Book.class).getResultList();

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.

15:38:36,020 DEBUG [org.hibernate.SQL] – select book0_.id as id1_2_, book0_.publishingDate as publishi2_2_, book0_.title as title3_2_, book0_.version as version4_2_, book0_.pages as pages5_2_ from Book book0_

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. This mapping allows you to use polymorphic queries and to define associations to the superclass. However, the table structure adds a lot of complexity to polymorphic queries, and you should better avoid them.

When using the table-per-class strategy, each concrete class is mapped to its own database table. In this example, I defined the Publication class as abstract. Due to that, it doesn’t get mapped to a database table, but you can use it in polymorphic queries and association mappings.

tablePerClass

The definition of the superclass using 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.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Publication {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	protected Long id;

	protected String title;

	@Version
	private int version;

        @ManyToMany
        @JoinTable(name = "PublicationAuthor", 
                   joinColumns = { @JoinColumn(name = "publicationId", referencedColumnName = "id") }, 
                   inverseJoinColumns = { @JoinColumn(name = "authorId", referencedColumnName = "id") })
        private Set authors = new HashSet();

	private LocalDate publishingDate;

	…
}

The definitions of the Book and BlogPost entities are identical to the previously discussed strategy. You have to extend the Publication class, add the @Entity annotation, and add the class-specific attributes with their mapping annotations.

@Entity
public class Book extends Publication {

	private int pages;

	…
}
@Entity
public class BlogPost extends Publication {

	private String url;

	…
}

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.

List books = em.createQuery("SELECT b FROM Book b", Book.class).getResultList();
15:56:21,463 DEBUG [org.hibernate.SQL] – select book0_.id as id1_3_, book0_.publishingDate as publishi2_3_, book0_.title as title3_3_, book0_.version as version4_3_, book0_.pages as pages1_2_ from Book book0_

The superclass is now also an entity. You can use it to define an association between the Author and Publication entities. This allows you to call the getPublications() method to get all Publications written by that Author.

When processing the result of a polymorphic query, Hibernate maps each record to the right class. So, in this example, Hibernate maps the query result to Book and BlogPost objects.

List authors = em.createQuery("SELECT a FROM Author a", Author.class).getResultList();
for (Author a : authors) {
	for (Publication p : a.getPublications()) {
		if (p instanceof Book)
		log(p.getTitle(), "book");
		else
		log(p.getTitle(), "blog post");
	}
}

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.

15:57:16,722 DEBUG [org.hibernate.SQL] – select author0_.id as id1_0_, author0_.firstName as firstNam2_0_, author0_.lastName as lastName3_0_, author0_.version as version4_0_ from Author author0_
15:57:16,765 DEBUG [org.hibernate.SQL] – select publicatio0_.authorId as authorId2_4_0_, publicatio0_.publicationId as publicat1_4_0_, publicatio1_.id as id1_3_1_, publicatio1_.publishingDate as publishi2_3_1_, publicatio1_.title as title3_3_1_, publicatio1_.version as version4_3_1_, publicatio1_.pages as pages1_2_1_, publicatio1_.url as url1_1_1_, publicatio1_.clazz_ as clazz_1_ from PublicationAuthor publicatio0_ inner join ( select id, publishingDate, title, version, null::int4 as pages, null::varchar as url, 0 as clazz_ from Publication union all select id, publishingDate, title, version, pages, null::varchar as url, 1 as clazz_ from Book union all select id, publishingDate, title, version, null::int4 as pages, url, 2 as clazz_ from BlogPost ) publicatio1_ on publicatio0_.publicationId=publicatio1_.id where publicatio0_.authorId=?
Effective Java is a book.

We store books and blog posts in separate tables. So, when generating the query, 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.

Due to that, the table-per-class strategy shouldn’t be your first choice if you expect many polymorphic associations or queries. The single table and the joined strategy are a much better fit for those applications.

Single Table

The single table strategy maps all entities of the inheritance hierarchy to the same database table. This approach makes polymorphic queries very efficient and generally 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.

singleTable

When you persist all entities in the same table, Hibernate needs a way to determine the entity class each record represents. This 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.

If you have to map a legacy database that doesn’t provide a discriminator column, you can use a @DiscriminatorFormular instead. It expects an SQL snippet that returns the discriminator value for each class of your hierarchy. This approach is more complex and usually doesn’t perform as well as a discriminator column. I, therefore, don’t explain it in this article. But if you want to learn more about it, you learn everything you need in my guide to the @DiscriminatorFormular annotation.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Publication_Type")
public abstract class Publication {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	protected Long id;

	protected String title;

	@Version
	private int version;

        @ManyToMany
        @JoinTable(name = "PublicationAuthor", 
                   joinColumns = { @JoinColumn(name = "publicationId", referencedColumnName = "id") }, 
                   inverseJoinColumns = { @JoinColumn(name = "authorId", referencedColumnName = "id") })
        private Set authors = new HashSet();

	private LocalDate publishingDate;

	…
}

The definition of the subclasses is 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 the right 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. The JPA specification doesn’t define this default handling, so you should avoid it if you want to create a portable persistence layer.

@Entity
@DiscriminatorValue("Book")
public class Book extends Publication {

	private int pages;

	…
}
@Entity
@DiscriminatorValue("Blog")
public class BlogPost extends Publication {

	private String url;

	…
}

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‘.

List books = em.createQuery("SELECT b FROM Book b", Book.class).getResultList();
16:02:47,411 DEBUG [org.hibernate.SQL] – select book0_.id as id2_1_, book0_.publishingDate as publishi3_1_, book0_.title as title4_1_, book0_.version as version5_1_, book0_.pages as pages6_1_ from Publication book0_ where book0_.Publication_Type=’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 of such a query.

List authors= em.createQuery("SELECT a FROM Author a", Author.class).getResultList();
for (Author a : authors) {
	for (Publication p : a.getPublications()) {
		if (p instanceof Book)
		log(p.getTitle(), "book");
		else
		log(p.getTitle(), "blog post");
	}
}

As you can see in the log messages, Hibernate selects all columns from the publication table, including the discriminator column publication_type. It then uses the discriminator value to choose the right entity class and map the database record. This query is much easier than the one created by the table-per-class strategy and generally causes fewer performance problems.

16:04:32,073 DEBUG [org.hibernate.SQL] – select author0_.id as id1_0_, author0_.firstName as firstNam2_0_, author0_.lastName as lastName3_0_, author0_.version as version4_0_ from Author author0_
16:04:32,118 DEBUG [org.hibernate.SQL] – select publicatio0_.authorId as authorId2_2_0_, publicatio0_.publicationId as publicat1_2_0_, publicatio1_.id as id2_1_1_, publicatio1_.publishingDate as publishi3_1_1_, publicatio1_.title as title4_1_1_, publicatio1_.version as version5_1_1_, publicatio1_.pages as pages6_1_1_, publicatio1_.url as url7_1_1_, publicatio1_.Publication_Type as Publicat1_1_1_ from PublicationAuthor publicatio0_ inner join Publication publicatio1_ on publicatio0_.publicationId=publicatio1_.id where publicatio0_.authorId=?
Effective Java is a book.

Joined

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 approach maps each subclass to multiple tables. The abstract superclass Publication is mapped to its own database table. It contains the columns shared by all classes. The tables of the subclasses are much smaller than in the table-per-class strategy. They hold only the columns specific to the mapped entity class and share the primary key with the corresponding record in the superclass table.

joinedTable

Each query of a subclass requires a join to the table of the superclass. That increases the complexity of each query, but it also allows you to use subclass-specific constraints to ensure data integrity, e.g., a not null constraint on the url column of the blogpost table.

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.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Publication {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        protected Long id;

        protected String title;

        @Version
        private int version;

        @ManyToMany
        @JoinTable(name = "PublicationAuthor", 
                   joinColumns = { @JoinColumn(name = "publicationId", referencedColumnName = "id") }, 
                   inverseJoinColumns = { @JoinColumn(name = "authorId", referencedColumnName = "id") })
        private Set authors = new HashSet();

        private LocalDate publishingDate;

        …
}

The definition of the subclasses doesn’t require any additional annotations. They extend the superclass, provide an @Entity annotation, and define the mapping of their specific attributes.

@Entity
public class Book extends Publication {

	private int pages;

	…
}
@Entity
public class BlogPost extends Publication {

	private String url;

	…
}

As I already explained, the columns mapped by each subclass are stored in separate database tables. The publication table contains all columns mapped by the superclass Publication, and the book table contains 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.

List books = em.createQuery("SELECT b FROM Book b", Book.class).getResultList();
15:56:21,463 DEBUG [org.hibernate.SQL] – select book0_.id as id1_3_, book0_.publishingDate as publishi2_3_, book0_.title as title3_3_, book0_.version as version4_3_, book0_.pages as pages1_2_ from Book book0_

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.

List authors= em.createQuery("SELECT a FROM Author a", Author.class).getResultList();
for (Author a : authors) {
	for (Publication p : a.getPublications()) {
		if (p instanceof Book)
		log(p.getTitle(), "book");
		else
		log(p.getTitle(), "blog post");
	}
}
17:16:05,244 DEBUG [org.hibernate.SQL] – select author0_.id as id1_0_, author0_.firstName as firstNam2_0_, author0_.lastName as lastName3_0_, author0_.version as version4_0_ from Author author0_
17:16:05,280 DEBUG [org.hibernate.SQL] – select publicatio0_.authorId as authorId2_4_0_, publicatio0_.publicationId as publicat1_4_0_, publicatio1_.id as id1_3_1_, publicatio1_.publishingDate as publishi2_3_1_, publicatio1_.title as title3_3_1_, publicatio1_.version as version4_3_1_, publicatio1_1_.pages as pages1_2_1_, publicatio1_2_.url as url1_1_1_, case when publicatio1_1_.id is not null then 1 when publicatio1_2_.id is not null then 2 when publicatio1_.id is not null then 0 end as clazz_1_ from PublicationAuthor publicatio0_ inner join Publication publicatio1_ on publicatio0_.publicationId=publicatio1_.id left outer join Book publicatio1_1_ on publicatio1_.id=publicatio1_1_.id left outer join BlogPost publicatio1_2_ on publicatio1_.id=publicatio1_2_.id where publicatio0_.authorId=?
Effective Java is a book.

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 drawbacks you can accept for your application. Here are a few recommendations:

  • If you require the best performance and need to use polymorphic queries and associations, you should choose the single table strategy. But be aware that you can’t use not null constraints on subclass attributes, which increases the risk of data inconsistencies.
  • If data consistency is more important than performance and you need polymorphic queries and associations, the joined strategy is usually your best option.
  • If you don’t need polymorphic queries or associations, my recommendation depends on the kind of inheritance hierarchy you’re modeling.
    • If all elements of the inheritance hierarchy exist in your application’s domain, your requirements might change, and you might need polymorphic queries in the future. In this case, the table-per-class strategy is most likely your best option. It allows you to use constraints to ensure data consistency and provides an option for polymorphic queries. But keep in mind that polymorphic queries are very complex for this table structure and that you should avoid them.
    • Sometimes, not all elements of the inheritance hierarchy exist in your application’s domain. This is often the case if you introduce a technical superclass to ensure all your entities have a standard set of attributes.
      In this case, the mapped superclass strategy is your best choice. It’s the simplest and most efficient way to share a set of attributes with their mapping definitions.

Similar Posts

33 Comments

  1. Deepak Lalchandani says:

    Hi thorben,
    Where is the workable Java code uploaded for Inheritance Strategies with JPA and Hibernate – The Complete Guide so that I can see the code for all 4 scenarios.

    Deepak

    1. Thorben Janssen says:

      The code was embedded from github, but that seemed to cause issues on some browsers. We’re now hosting the code locally and that issues should be fixed.

  2. Thanks, very nice and clear explained!

    1. Thorben Janssen says:

      Thanks, Julien!

  3. Philipp Hundelshausen says:

    I think in the Single Table code the @Table annotation is missing.

    1. Thorben Janssen says:

      No, the @Table annotation is not required. The persistence provider will use the default table mapping of the superclass for all entities.
      So, in this example, Book and BlogPost entities will be mapped to the PUBLICATION table.

  4. Hey Thorben
    I found a solution to my earlier question: I have to add the @ManyToOne relationships to the Publication entity. Then use left join fetch to get all the data in one query. With eclipselink this works.

    Also noteable with eclipselink the entities in the the joined example require @DiscriminatorValue and @DiscriminatorColumn. This seems to be unspecified in jpa (v2.1).

    cheers

    1. Thorben Janssen says:

      Hi Marco,

      Yes, that’s right. All attributes that are shared by all subclasses go into the superclass. You can use these attribute in your queries even if you’re operating on the superclass.
      You can also cast an entity in your query to a specific subclass using the TREAT function.

      Regards,
      Thorben

  5. Very good article Thorben.

    I have a question about the single table example:
    Assume the Book and Blog Entities have a an additional relationship. E.g Book looks like:

    @Entity(name = “Book”)
    @DiscriminatorValue(“Book”)
    public class Book extends Publication {

    @Column
    private int pages;
    @ManyToOne
    private BookPublisher

    }

    If I query the Publication entity via jpql/criteria: How can I join fetch the BookPublisher relationsship? I know how I can join fetch the relationship if I query the Book entity. But how to do it if I query the Publication entity and its subclasses?

  6. Where is the getPublications() defined? Author Class doesn’t have this method.

    1. Thorben Janssen says:

      In that example, the getPublications() method is defined on the Author class.

  7. Great job Thorben. Easy and understandable.
    Thanks.

  8. Christopher Boyer says:

    Very great writeup! Wouldn’t the single table strategy also have a drawback of space? A book is never going to have a url, so every book entity in the publication table is going to have wasted space. That problem will only get worse as more subclasses are added.

    1. Thorben Janssen says:

      Empty fields don’t take up space, but having lots of null values in your table might create performance problems on your database.

  9. Very nice comparison between different strategies, article is to the point and crystal clear, Thank you

  10. excellent description, specialy the pros and cons (i have used entity framework strategies (TPH, TPC, TPT and never considered the null-constraint con of TPH (in jpa single table))

    1. Thorben Janssen says:

      Yes, that’s an often ignored side-effect of that strategy. It doesn’t have to be a big issue if you implement and test your application carefully. But you should be aware of it …

  11. Great Article , Cristal Clear !!

    1. Thorben Janssen says:

      Thanks, Samara

  12. Thanks easy to understand.

    1. Thorben Janssen says:

      Thanks, happy to help

  13. Salah Alhaddabi says:

    Thanks a lot and a very nice comparison that helps a lot when it comes to decide on your strategy

  14. Ehsan Soleimani says:

    Very useful blog. thank you

    1. Thorben Janssen says:

      Thanks Ehsan

  15. Thank you for explaining every strategy

    1. Thorben Janssen says:

      You’re welcome 🙂
      And thanks for your comment.

  16. Robert Sullivan says:

    Thorben, Very nice explanation and diagrams.

    1. Thorben Janssen says:

      Thanks, Robert

  17. Binh Thanh Nguyen says:

    Thanks, nice explanation!

  18. Hi Thorben,

    Good post.
    But you forgot to mention that support for the TABLE_PER_CLASS mapping strategy is optional according to the JPA spec.

    Regards, Simon

    1. Thorben Janssen says:

      Good point.
      Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.