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.

hibernate-jpa-table-per-class-inheritance-database-diagram

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>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

We have the following polymorphic classes.

hibernate-jpa-single-table-inheritance-example

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_

References

Downloads

You may also like...