JPA - Table per Class Inheritance Strategy

[Updated: Jul 10, 2017, Created: Jul 10, 2017]
A quick overview of JPA Table per class (concrete) inheritance strategy.
  • In this strategy, the superclass and subclasses in a hierarchy are mapped to different individual tables.
  • All super/subclasses tables store all fields of that class plus the ones which are inherited from the super class.
  • The annotation @Inheritance is used on the root entity class with
    strategy = InheritanceType.TABLE_PER_CLASS.
  • @DiscriminatorColumn and @DiscriminatorValue are not required to be used in this strategy.
  • @Entity and other meta-data annotations are used on the root and subclasses as usual.
  • @Id field should only be defined in the root class.
  • The root class can be abstract or a concrete class.
  • For JPA implementations, support for the table per concrete class inheritance mapping strategy is optional. That means applications that use this mapping strategy might not be portable.
  • This strategy has the disadvantage of repeating same attributes in the tables. This strategy also uses SQL UNION queries (or a separate SQL query per subclass). When the concrete subclass is not known, the related object could be in any of the subclass tables, making joins through the relation impossible, hence providing poor support for polymorphic relationships.

Example

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
    .............
}
@Entity
@Table(name = "FULL_TIME_EMP")
public class FullTimeEmployee extends Employee {
  private int salary;
    .............
}
@Entity
@Table(name = "PART_TIME_EMP")
public class PartTimeEmployee extends Employee {
  private int hourlyRate;
    .............
}
public class ExampleMain {

  public static void main(String[] args) {
      EntityManagerFactory emf =
              Persistence.createEntityManagerFactory("example-unit");
      try {
          EntityManager em = emf.createEntityManager();
          nativeQuery(em, "SHOW TABLES");
          nativeQuery(em, "SHOW COLUMNS from EMPLOYEE");
          nativeQuery(em, "SHOW COLUMNS from FULL_TIME_EMP");
          nativeQuery(em, "SHOW COLUMNS from PART_TIME_EMP");

      } finally {
          emf.close();
      }
  }
    .............
}

Output

'SHOW TABLES'
[EMPLOYEE, PUBLIC]
[FULL_TIME_EMP, PUBLIC]
[PART_TIME_EMP, PUBLIC]
'SHOW COLUMNS from EMPLOYEE'
[ID, BIGINT(19), NO, PRI, NULL]
[NAME, VARCHAR(255), YES, , NULL]
'SHOW COLUMNS from FULL_TIME_EMP'
[ID, BIGINT(19), NO, PRI, NULL]
[NAME, VARCHAR(255), YES, , NULL]
[SALARY, INTEGER(10), NO, , NULL]
'SHOW COLUMNS from PART_TIME_EMP'
[ID, BIGINT(19), NO, PRI, NULL]
[NAME, VARCHAR(255), YES, , NULL]
[HOURLYRATE, INTEGER(10), NO, , NULL]

A quick overview of the mapping:

Persisting and loading data

public class ExampleMain2 {

  public static void main(String[] args) throws Exception {
      EntityManagerFactory emf =
              Persistence.createEntityManagerFactory("example-unit");
      try {
          persistEntities(emf);
          runNativeQueries(emf);
          loadEntities(emf);
      } finally {
          emf.close();
      }
  }

  private static void persistEntities(EntityManagerFactory emf) throws Exception {
      System.out.println("-- Persisting entities --");
      EntityManager em = emf.createEntityManager();

      FullTimeEmployee e1 = new FullTimeEmployee();
      e1.setName("Sara");
      e1.setSalary(100000);
      System.out.println(e1);

      PartTimeEmployee e2 = new PartTimeEmployee();
      e2.setName("Robert");
      e2.setHourlyRate(60);
      System.out.println(e2);

      em.getTransaction().begin();
      em.persist(e1);
      em.persist(e2);
      em.getTransaction().commit();
      em.close();
  }

  private static void runNativeQueries(EntityManagerFactory emf) {
      System.out.println("-- Native queries --");
      EntityManager em = emf.createEntityManager();
      ExampleMain.nativeQuery(em, "Select * from Employee");
      ExampleMain.nativeQuery(em, "Select * from FULL_TIME_EMP");
      ExampleMain.nativeQuery(em, "Select * from PART_TIME_EMP");
  }

  private static void loadEntities(EntityManagerFactory emf) {
      System.out.println("-- Loading entities --");
      EntityManager em = emf.createEntityManager();
      List<Employee> entityAList = em.createQuery("Select t from Employee t")
                                     .getResultList();
      entityAList.forEach(System.out::println);
      em.close();
  }
}

Output

-- Persisting entities --
FullTimeEmployee{id=0, name='Sara', salary=100000}
PartTimeEmployee{id=0, name='Robert', hourlyRate='60'}
-- Native queries --
'Select * from Employee'
'Select * from FULL_TIME_EMP'
[1, Sara, 100000]
'Select * from PART_TIME_EMP'
[2, Robert, 60]
-- Loading entities --
PartTimeEmployee{id=2, name='Robert', hourlyRate='60'}
FullTimeEmployee{id=1, name='Sara', salary=100000}

Example Project

Dependencies and Technologies Used :

  • h2 1.4.196: H2 Database Engine.
  • hibernate-core 5.2.10.Final: The core O/RM functionality as provided by Hibernate.
  • JDK 1.8
  • Maven 3.3.9

Table Per Class Inheritance Example Select All Download
  • jpa-table-per-class-inheritance
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also