Featured Image with Sidebar

Inheritance Strategies with JPA and Hibernate – The Complete Guide

By Thorben Janssen

Mapping

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 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 which map the domain objects to different table structures.

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

@MappedSuperclass
public abstract class Publication {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = “id”, updatable = false, nullable = false)
	protected Long id;

	@Column
	protected String title;

	@Version
	@Column(name = “version”)
	private int version;

	@Column
	@Temporal(TemporalType.DATE)
	private Date publishingDate;

	…
}

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.

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

	@Column
	private int pages;

	…
}
@Entity(name = “BlogPost”)
public class BlogPost extends Publication {

	@Column
	private String url;

	…
}

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

tablePerClass

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.

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

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = “id”, updatable = false, nullable = false)
	protected Long id;

	@Column
	protected String title;

	@Version
	@Column(name = “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();

	@Column
	@Temporal(TemporalType.DATE)
	private Date publishingDate;

	…
}

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.

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

	@Column
	private int pages;

	…
}
@Entity(name = “BlogPost”)
public class BlogPost extends Publication {

	@Column
	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 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.

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.

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.

Single Table

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.

singleTable

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.

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

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = “id”, updatable = false, nullable = false)
	protected Long id;

	@Column
	protected String title;

	@Version
	@Column(name = “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();

	@Column
	@Temporal(TemporalType.DATE)
	private Date publishingDate;

	…
}

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.

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

	@Column
	private int pages;

	…
}
@Entity(name = “BlogPost”)
@DiscriminatorValue(“Blog”)
public class BlogPost extends Publication {

	@Column
	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 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.

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”);
	}
}
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 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.

joinedTable

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.

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

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = “id”, updatable = false, nullable = false)
        protected Long id;

        @Column
        protected String title;

        @Version
        @Column(name = “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();

        @Column
        @Temporal(TemporalType.DATE)
        private Date publishingDate;

        …
}

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.

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

	@Column
	private int pages;

	…
}
@Entity(name = “BlogPost”)
public class BlogPost extends Publication {

	@Column
	private String url;

	…
}

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.

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

About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

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.

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

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

  2. 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?

  3. 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. Empty fields don’t take up space, but having lots of null values in your table might create performance problems on your database.

  4. 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. 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 …

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}