Hibernate/JPA Joined Table 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 joined table inheritance.
The base class and all the subclasses have their own database table. Fetching a subclass entity requires a join with the parent table as well.
The JOINED table
inheritance strategy addresses the data integrity concerns because every subclass is associated with a different table. Polymorphic queries or @OneToMany
base class associations don’t perform very well with this strategy. However, polymorphic @ManyToOne
associations are fine, and they can provide a lot of value.
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
Each subclass is mapped to its own table. This is also called table-per-subclass mapping strategy. An inherited state is retrieved by joining with the table of the superclass. A discriminator column is not required for this mapping strategy. Each subclass must, however, declare a table column holding the object identifier.
The inheritance strategy is defined on the abstract super class, using the @Inheritance
annotation. In this example, we used InheritanceType.JOINED
. This means, all concrete subclasses and superclass will be stored their own table. You can optionally specify a discriminator column name. This column is registered by the @DiscriminatorColumn
, if omitted no default is used.
package com.memorynotfound.hibernate;
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Shape {
@Id
@Column(name = "vehicle_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer 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 +
'}';
}
}
The primary key of the concrete subclass is also a foreign key to the superclass table. If the @PrimaryKeyJoinColumn
is not set, the primary key / foreign key columns are assumed to have the same names as the primary key columns of the primary table of the superclass.
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.0">
<!-- abstract shape -->
<entity class="com.memorynotfound.hibernate.Shape">
<inheritance strategy="JOINED"/>
<attributes>
<id name="id">
<generated-value strategy="IDENTITY"/>
<column name="shape_id"/>
</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">
<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 JOINED
inheritance strategy, all subclasses and superclass have their own table.
create table Circle (
radius double precision not null,
shape_id integer not null,
primary key (shape_id)
)
create table Rectangle (
length double precision not null,
width double precision not null,
shape_id integer not null,
primary key (shape_id)
)
create table Shape (
shape_id integer not null auto_increment,
primary key (shape_id)
)
alter table Circle
add constraint FK2nshngrop6dt5amv1egecvdnn
foreign key (shape_id)
references Shape (shape_id)
alter table Rectangle
add constraint FKh3gkuyk86e8sfl6ilsulitcm5
foreign key (shape_id)
references Shape (shape_id)
Here is the SQL INSERT statement.
insert into Shape values( )
insert into Circle (radius, shape_id) values (?, ?)
insert into Shape values ( )
insert into Rectangle (length, width, shape_id) values (?, ?, ?)
When using polymorphic queries, the base class table must be joined with all subclass tables to fetch every associated subclass instance. The joined table inheritance polymorphic queries can use several JOINS which might affect performance when fetching a large number of entities.
select
shape0_.shape_id as shape_id1_2_,
shape0_1_.radius as radius1_0_,
shape0_2_.length as length1_1_,
shape0_2_.width as width2_1_,
case
when shape0_1_.shape_id is not null then 1
when shape0_2_.shape_id is not null then 2
when shape0_.shape_id is not null then 0
end as clazz_
from
Shape shape0_
left outer join
Circle shape0_1_
on shape0_.shape_id=shape0_1_.shape_id
left outer join
Rectangle shape0_2_
on shape0_.shape_id=shape0_2_.shape_id