Hibernate/JPA Single 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 single table inheritance.

hibernate-jpa-single-table-inheritance-class-diagram

The single table inheritance strategy maps all subclasses to only one database table. Each subclass declares its own persistent properties. Version and id properties are assumed to be inherited from the root class.

When no explicit inheritance strategy is registered, Hibernate/JPA will choose the SINGLE_TABLE inheritance strategy by default.

SINGLE_TABLE inheritance performs the best in terms of executed SQL statements. However, you cannot use NOT NULL constraints on the column-level. You can still use triggers and rules to enforce such constraints, but it’s not as straightforward.

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 in a hierarchy must define a unique discriminator value, which is used to differentiate between rows belonging to separate subclass types. If this is not specified, the DTYPE column is used as a discriminator, storing the associated subclass name.

hibernate-jpa-single-table-inheritance-example

The inheritance strategy is defined on the abstract super class, using the @Inheritance annotation. In this example, we used InheritanceType.SINGLE_TABLE. This means, all concrete subclasses will be stored in one table. You can optionally specify a discriminator column name. This column is registered by the @DiscriminatorColumn, if omitted the default DTYPE name is used.

package com.memorynotfound.hibernate;

import javax.persistence.*;

@Entity
@DiscriminatorColumn(name = "type")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Shape {

    @Id
    @Column(name = "vehicle_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

}

The first concrete subclass named Circle.java is annotated with the @DiscriminatorValue annotation. Meaning the the discriminator value for this object.

package com.memorynotfound.hibernate;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value = "Circle")
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 second concrete subclass named Rectangle.java is annotated with the @DiscriminatorValue annotation. Meaning the the discriminator value for this object.

package com.memorynotfound.hibernate;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value = "Rectangle")
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="SINGLE_TABLE"/>
        <discriminator-column name="type"/>
        <attributes>
            <id name="id">
                <generated-value strategy="IDENTITY"/>
                <column name="shape_id"/>
            </id>
        </attributes>
    </entity>

    <!-- circle concrete subclass -->
    <entity class="com.memorynotfound.hibernate.Circle">
        <discriminator-value>circle</discriminator-value>
        <attributes>
            <basic name="radius"/>
        </attributes>
    </entity>

    <!-- rectangle concrete subclass -->
    <entity class="com.memorynotfound.hibernate.Rectangle">
        <discriminator-value>rectangle</discriminator-value>
        <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 SINGLE_TABLE inheritance strategy, a single table is created.

create table Shape (
    vehicle_id integer not null auto_increment,
    radius double precision,
    length double precision,
    width double precision,
    type varchar(31) not null,
    primary key (vehicle_id)
)

Here is the SQL INSERT statement.

insert into Shape (radius, type) values (?, 'Circle')
insert into Shape (length, width, type) values (?, ?, 'Rectangle')

The SQL SELECT statement of a SINGLE_TABLE inheritance is the fastest, because the select statement is executed for a single table.

select
    shape0_.shape_id as shape_id2_0_,
    shape0_.radius as radius3_0_,
    shape0_.length as length4_0_,
    shape0_.width as width5_0_,
    shape0_.type as type1_0_ 
from
    Shape shape0_

References

Downloads

You may also like...