hibernate

Hibernate Inheritance Mapping Example

In this post, we feature a comprehensive article about Hibernate Inheritance Mapping.

Inheritance is one of the fundamental design principles o Object-Oriented Languages. In Java, it is very common to have inheritance implemented in domain models. However, relational databases don’t support inheritance and they support flat structure.

Java Persistence API suggests different strategies to support inheritance hierarchies. In this article, we are going to study how to hibernate implements these strategies and how to map inheritance hierarchies.

1. What are we trying to solve (Domain model)?

We are trying to solve the below scenario in hibernate,

  • Representing employees in an organization
  • Employees can be classified as Contract Employees and Permanent Employees
  • General employee attributes are defined in Employee superclass
  • ContractEmployee and PermanentEmployee are subclasses and they have more specific attributes

Class diagram of our entity classes is as below,

Hibernate Inheritance - Class Diagram
Class Diagram

2. Hibernate Inheritance strategies

2.1 Single Table Strategy

This is typically the best inheritance strategy. In this strategy, whole inheritance hierarchy’s data is stored in a single table. A discriminator column is used to determine to which class the row belongs.

Hibernate Inheritance - Single Table Strategy
Single Table Strategy

All the Contract and Permanent employee details are stored in the employee table and they are differentiated by discriminator column employee_type. Below is the domain class structure,

Single Table Strategy entity classes

@Entity
@Table(name = "EMPLOYEE")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "EMPLOYMENT_TYPE")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    @Id
    @GeneratedValue( strategy = GenerationType.AUTO )
    private Long id;
    @Column(name = "EMPLOYEE_ID")
    private String employeeId;
    private String firstName;
    private String lastName;
    private String email;
}

@Entity
@DiscriminatorValue("CONTRACT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ContractEmployee extends Employee {
    private LocalDate contractStartDate;
    private LocalDate contractEndDate;
    private String agencyName;
}

@Entity
@DiscriminatorValue("PERMANENT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PermanentEmployee extends Employee {
    private LocalDate startingDate;
    private String costCenter;
    private Float totalLeaves;
}

As you see only the parent class has a database table associated with it and hibernate automatically creates a discriminatory column employee_type for us.

Implementing this strategy becomes quite tricky when the child table has nullable values and also if you are implementing an ORM solution to an existing database schema as there may not be any discriminator column.

2.2 Table per Class Strategy

In table per class strategy, each concrete class has an associated database table and stores all the attributes in the class hierarchy to store all the attributes of that class and its parent class.

Hibernate Inheritance - table per class
table per class

In table per class hierarchy, each class in the hierarchy has a mapping database table. Contract employee details are stored in table_per_class_contract_employee and permanent employee details are stored in table_per_class_permanent_employee table respectively. Since they are stored in different tables, there is no need to have a discriminator column. Entity class structure is as below,

Table per Class Strategy entity classes

@Entity(name = "tablePerClassEmployee")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    @Id
    @GeneratedValue( strategy = GenerationType.AUTO )
    private Long id;
    @Column(name = "EMPLOYEE_ID")
    private String employeeId;
    private String firstName;
    private String lastName;
    private String email;
}

@Entity(name = "tablePerClassContractEmployee")
@Table(name = "TABLE_PER_CLASS_CONTRACT_EMPLOYEE")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ContractEmployee extends Employee {
    private LocalDate contractStartDate;
    private LocalDate contractEndDate;
    private String agencyName;
}

@Entity(name = "tablePerClassPermanentEmployee")
@Table(name = "TABLE_PER_CLASS_PERMANENT_EMPLOYEE")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PermanentEmployee extends Employee {
    private LocalDate startingDate;
    private String costCenter;
    private Float totalLeaves;
}

Less performant as a result of additional joins. Some JPA providers even don’t support this strategy. Sometimes ordering is quite tricky as ordering is done based on the class and later by the ordering criteria.

2.3 Mapped Super Class strategy

It is like table per class strategy but it does not allow querying, persisting or any relationships to parent class’s table. The mapped superclass should be an abstract class and isn’t marked with @Entity annotation.

Hibernate Inheritance - mapped super class
mapped superclass

In mapped superclass strategy parent class isn’t mapped to any database table. However, the database table mapping to child class contains all the attributes of the parent class. Below is the code for entity classes,

Mapped Superclass Strategy entity classes

@MappedSuperclass
@DiscriminatorColumn(name = "EMPLOYMENT_TYPE")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    @Id
    @GeneratedValue( strategy = GenerationType.AUTO )
    private Long id;
    @Column(name = "EMPLOYEE_ID")
    private String employeeId;
    private String firstName;
    private String lastName;
    private String email;
}

@Entity(name = "mappedSuperClassContractEmployee")
@Table(name = "MAPPED_CONTRACT_EMPLOYEE")
@PrimaryKeyJoinColumn(name = "ID")
@DiscriminatorValue("CONTRACT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ContractEmployee extends Employee {
    private LocalDate contractStartDate;
    private LocalDate contractEndDate;
    private String agencyName;
}

@Entity(name = "mappedSuperClassPermanentEmployee")
@Table(name = "MAPPED_PERMANENT_EMPLOYEE")
@PrimaryKeyJoinColumn(name = "ID")
@DiscriminatorValue("PERMANENT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PermanentEmployee extends Employee {
    private LocalDate startingDate;
    private String costCenter;
    private Float totalLeaves;
}

The subclass cannot omit the superclass’s attributes. This makes the classes tightly coupled. Also, you cannot have any relationships to the mapped superclass, they can’t be queried or persisted separately.

2.4 Joined Table Strategy

This is the most logical solution, as it mirrors the object structure in the database. In this approach, a separate database table is defined for each of the class in the hierarchy and each table stores only its local attributes. Along with attribute, each table should have an id column and the id is defined in the parent table.

join table strategy

Each of the entity classes is associated with a database table. Additionally, the parent class’s mapped table contains a discriminatory column. In our demo, join_table_employee contains the employee_type discriminatory column. Below is the entity structure,

Joined Table Strategy entity classes

@Entity(name = "joinedTableEmployee")
@Table(name = "JOIN_TABLE_EMPLOYEE")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "EMPLOYMENT_TYPE")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    @Id
    @GeneratedValue( strategy = GenerationType.AUTO )
    private Long id;
    @Column(name = "EMPLOYEE_ID")
    private String employeeId;
    private String firstName;
    private String lastName;
    private String email;
}

@Entity(name = "joinedTableContractEmployee")
@Table(name = "JOIN_TABLE_CONTRACT_EMPLOYEE")
@PrimaryKeyJoinColumn(name = "ID")
@DiscriminatorValue("CONTRACT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ContractEmployee extends Employee {
    private LocalDate contractStartDate;
    private LocalDate contractEndDate;
    private String agencyName;
}

@Entity(name = "joinedTablePermanentEmployee")
@Table(name = "JOIN_TABLE_PERMANENT_EMPLOYEE")
@PrimaryKeyJoinColumn(name = "ID")
@DiscriminatorValue("PERMANENT")
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PermanentEmployee extends Employee {
    private LocalDate startingDate;
    private String costCenter;
    private Float totalLeaves;
}

Generally, this approach is less performant as a result of unnecessary joins. Additionally, if you are migrating from an existing database, the discriminator column may be missing. Sometimes we may not need a table for every subclass.

3. Example

Refer this article to configure hibernate in your project.

In this example, I am using the Java-based configuration. The project is built using Java 11, Hibernate 5.x and Intellij Idea editor. For the database, I am using PostgreSQL.

You need to install a project in Lombok’s plugin. Lombok annotations help to keep the code clean and can reduce many boilerplate codes like getters and setters, all args and no-args constructors.

How to run the demo application?

  • Download the source code and import it as a Maven project in IntelliJ idea
  • All the entities are present in com.jcg.entity package and main classes for each of the strategies are present under com.jcg.demo package
  • Hibernate’s table generation strategy is set to `create-drop`. Each time you run a program it recreates the table and previously saved data is lost
  • To see the demo working run each of the main class separately and after the successful run check the respective database tables

Further enhancements which are worth to try are as follows,

  • Change the settings of HBM2DDL_AUTO` in HibernateUtil to see how the programs behave
  • Try with package-level mapping instead of mapping individual classes

4. Download the Source code

Download
You can download the full source code of this example here: Hibernate Inheritance Mapping Example

Santosh Balgar

He is a Software Engineer working in an industry-leading organization. He has completed his bachelors from Visweswaraya Technological University. In his career, he has worked in designing and implementing various software systems involving Java/J2EE, Spring/ Spring Boot, React JS, JQuery, Hibernate and related database technologies. He loves to share his knowledge and always look forward to learning and explore new technologies. He loves to spend his free time with his family. He enjoys traveling and loves to play cricket.
Subscribe
Notify of
guest

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

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nagesh
Nagesh
2 years ago

Mapped Superclass is not mandatory to be abstract as per the official hibernate docs.
Reference:
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#entity-inheritance-mapped-superclass

Nagesh
Nagesh
2 years ago

Mapped superclass strategy doesn’t need @DiscriminatorCloumn & @DiscriminatorValue

Back to top button