Hibernate/JPA Table Per Class Inheritance Example
In this tutorial, we show how to map entity hierarchies onto database tables. There are several ways of mapping inheritance to the database. Here, we’ll look into Hibernate/JPA table per class inheritance.
In a Table per class
inheritance strategy, each concrete subclass has its own table containing both the subclass and the base class properties.
Maven Dependencies
We use Apache Maven to manage the projects dependencies.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.memorynotfound.db.hibernate.configuration</groupId>
<artifactId>entity-manager</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>HIBERNATE - ${project.artifactId}</name>
<url>https://memorynotfound.com</url>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.3.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.3.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Creating Models + Hibernate/JPA Mappings
We have the following polymorphic classes.
We cannot use the SequenceType.SEQUENCE
generation strategy. Instead we can create a custom TABLE generation strategy for automatically creating the id’s. We do this by using the @TableGenerator
annotation and pass it in the @GeneratedValue
annotation.
package com.memorynotfound.hibernate;
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Shape {
@Id
@TableGenerator(
name = "SHAPE_GEN",
table = "ID_Generator",
pkColumnName = "name",
valueColumnName = "sequence",
allocationSize = 1)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "SHAPE_GEN")
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
package com.memorynotfound.hibernate;
import javax.persistence.Entity;
@Entity
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
package com.memorynotfound.hibernate;
import javax.persistence.Entity;
@Entity
public class Rectangle extends Shape {
private double width;
private double length;
public Rectangle(double width, double length) {
this.width = width;
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
@Override
public String toString() {
return "Rectangle{" +
"width=" + width +
", length=" + length +
'}';
}
}
If you prefer XML over Annotations, you can use the equivalent JPA XML mapping. This file is located in the src/main/resources/META-INF
folder and is named orm.xml
.
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_0.xsd" version="2.1">
<!-- abstract shape -->
<entity class="com.memorynotfound.hibernate.Shape">
<inheritance strategy="TABLE_PER_CLASS"/>
<table-generator name="SHAPE_GEN"
table="ID_Generator"
pk-column-name="name"
pk-column-value="sequecne"
allocation-size="1"/>
<attributes>
<id name="id">
<generated-value strategy="TABLE" generator="SHAPE_GEN"/>
</id>
</attributes>
</entity>
<!-- circle concrete subclass -->
<entity class="com.memorynotfound.hibernate.Circle">
<attributes>
<basic name="radius"/>
</attributes>
</entity>
<!-- rectangle concrete subclass -->
<entity class="com.memorynotfound.hibernate.Rectangle">
<attributes>
<basic name="length"/>
<basic name="width"/>
</attributes>
</entity>
</entity-mappings>
Hibernate/JPA Configuration
We configure the JPA Persistence Unit using the persistence.xml
file, which is located in the src/main/resources/META-INF
directory.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="mnf-pu" transaction-type="RESOURCE_LOCAL">
<!-- enable if you prefer xml over annotations -->
<!--<mapping-file>META-INF/orm.xml</mapping-file>-->
<properties>
<!-- Configuring JDBC properties -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memorynotfound?serverTimezone=Europe/Brussels"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- Hibernate properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
Create, Run and Test application
package com.memorynotfound.hibernate;
import javax.persistence.*;
public class App {
public static void main (String...args) throws InterruptedException {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mnf-pu");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Circle circle = new Circle(13.05);
em.persist(circle);
em.getTransaction().commit();
em.getTransaction().begin();
Rectangle rectangle = new Rectangle(5.02, 10.45);
em.persist(rectangle);
em.getTransaction().commit();
List accounts = em
.createQuery("select a from Shape a")
.getResultList();
emf.close();
}
}
Since we used the TABLE_PER_CLASS
inheritance strategy, a table is created for each concrete subclass.
create table Circle (
id integer not null,
radius double precision not null,
primary key (id)
)
create table Rectangle (
id integer not null,
length double precision not null,
width double precision not null,
primary key (id)
)
create table ID_Generator (
name varchar(255) not null,
sequence bigint,
primary key (name)
)
Here is the SQL INSERT statement.
select tbl.sequence from ID_Generator tbl where tbl.name=? for update
insert into ID_Generator (name, sequence) values (?,?)
update ID_Generator set sequence=? where sequence=? and name=?
insert into Circle (radius, id) values (?, ?)
select tbl.sequence from ID_Generator tbl where tbl.name=? for update
update ID_Generator set sequence=? where sequence=? and name=?
insert into Rectangle (length, width, id) values (?, ?, ?)
The SQL SELECT statement of a TABLE_PER_CLASS
inheritance requires multiple UNION queries, so be aware of the performance implications of a large class hiearchy.
select
shape0_.id as id1_2_,
shape0_.radius as radius1_0_,
shape0_.length as length1_1_,
shape0_.width as width2_1_,
shape0_.clazz_ as clazz_
from
( select
id,
radius,
null as length,
null as width,
1 as clazz_
from
Circle
union
select
id,
null as radius,
length,
width,
2 as clazz_
from
Rectangle
) shape0_