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.

hibernate-jpa-joined-table-inheritance-example

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>http://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.

hibernate-jpa-joined-table-inheritance-example

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

References

Downloads

You may also like...