Hibernate Inheritance strategy: Table Per Class Hierarchy

Introduction

I am going to give explanation and example on hibernate table per class hierarchy. Let’s consider you have a base class named Person and two derived classes – Student and Teacher.

If you save the derived class object like Student or Teacher then automatically Person class object will also be saved into the database, and in the database all data will be stored into a single table only, which is base class table for sure.

But here you must use one extra discriminator column in the database table to identify which derived class object you have  saved in the table along with the base class object. This discriminator column is mandatory otherwise hibernate will throw an exception.

hibernate inheritance strategy

In the above hierarchy, three classes are involved where Person is the super class and Student and Teacher are subclasses with their own properties declared as instance variables.

Related Posts:

Prerequisites

Java at least 8, Maven 3.6.3, Hibernate 6.0.0.Alpha8, MySQL 8.0.22

Project Setup

Create a maven based project in your favorite IDE or tool. The name of the project is hibernate-inheritance-table-per-class.

The required dependencies in pom.xml file can be given as follows:

<?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.roytuts</groupId>
	<artifactId>hibernate-inheritance-table-per-class</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>12</maven.compiler.source>
		<maven.compiler.target>12</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>6.0.0.Alpha8</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.22</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
		</plugins>
	</build>
</project>

MySQL Table

As the table per class inheritance hierarchy requires a single table for all classes so I am going to create a single table in MySQL database.

CREATE TABLE person_subject (
    id         int unsigned COLLATE utf8mb4_unicode_ci,
	name varchar(50) COLLATE utf8mb4_unicode_ci,
    discriminator  varchar(45) COLLATE utf8mb4_unicode_ci,
	year varchar(50) COLLATE utf8mb4_unicode_ci,
	subject varchar(50) COLLATE utf8mb4_unicode_ci,    
    PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

The discriminator column in the above table differentiates the derived classes used for inheritance.

Entity Classes

Here I am going to create the base entity class Person that will be extended by two sub-classes Student and Teacher and these sub-classes will inherit properties from the base class.

Person

The base class I am going to create as an abstract class. I am not using auto-increment value for id field so I am not using generation strategy for it.

I am specifying the inheritance strategy using annotation @Inheritance. I have specified the discriminator column name using the annotation @DiscriminatorColumn.

The table name I have already specified in this base class, so I won’t specify the table name in the sub-classes.

@Entity
@Table(name = "person_subject")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class Person {

	@Id
	// @GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@Column(name = "name")
	private String name;

	public Person() {
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

Student

The sub-class Student has the following signature. In this sub-class I have not specified the table name and it is going to take from the base class Person. I have specified the discriminator value for this derived or sub-class class.

@Entity
@DiscriminatorValue("yr")
public class Student extends Person {

	@Column(name = "year")
	private String year;

	public Student() {
	}

	public String getYear() {
		return year;
	}

	public void setYear(String year) {
		this.year = year;
	}

}

Teacher

The Teacher class has the following details with the discriminator value specified.

@Entity
@DiscriminatorValue("sub")
public class Teacher extends Person {

	@Column(name = "subject")
	private String subject;

	public Teacher() {
	}

	public String getSubject() {
		return subject;
	}

	public void setSubject(String subject) {
		this.subject = subject;
	}

}

Hibernate SessionFactory

The singleton SessionFactory class is needed to get non-thread safe session object from it. Here I am using annotation based configuration for building the session factory and mapping the entity classes.

public final class HibernateUtil {

	private HibernateUtil() {
	}

	private static SessionFactory sessionFactory;

	public static SessionFactory getSessionFactory() {

		if (sessionFactory == null) {
			try {
				Configuration configuration = configuration();
				ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
						.applySettings(configuration.getProperties()).build();

				sessionFactory = configuration.buildSessionFactory(serviceRegistry);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return sessionFactory;
	}

	private static Configuration configuration() {
		Properties settings = new Properties();

		settings.put(Environment.DRIVER, "com.mysql.cj.jdbc.Driver");
		settings.put(Environment.URL, "jdbc:mysql://localhost:3306/roytuts");
		settings.put(Environment.USER, "root");
		settings.put(Environment.PASS, "root");
		settings.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
		settings.put(Environment.SHOW_SQL, "true");
		settings.put(Environment.FORMAT_SQL, "true");
		settings.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread");
		settings.put(Environment.HBM2DDL_AUTO, "update"); // or create
		// Deprecated
		// settings.put(Environment.QUERY_TRANSLATOR,
		// "org.hibernate.hql.internal.classic.ClassicQueryTranslatorFactory");

		Configuration configuration = new Configuration();

		configuration.setProperties(settings);
		configuration.addAnnotatedClass(Person.class);
		configuration.addAnnotatedClass(Student.class);
		configuration.addAnnotatedClass(Teacher.class);

		return configuration;
	}

}

Testing the Application

I am writing a class with main method to test the application.

public class HibernateInheritanceApp {

	public static void main(String[] args) {

		Student s = new Student();
		Teacher t = new Teacher();

		s.setId(1);
		s.setName("Student");
		s.setYear("1st Year");

		t.setId(2);
		t.setName("Teacher");
		t.setSubject("Physics");

		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();

			session.save(s);
			session.save(t);

			tx.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
		}

	}

}

Now running the above main class you will see the following output in the console:

Hibernate: 
    insert 
    into
        person_subject
        (name, year, discriminator, id) 
    values
        (?, ?, 'yr', ?)
Hibernate: 
    insert 
    into
        person_subject
        (name, subject, discriminator, id) 
    values
        (?, ?, 'sub', ?)

You will also find that the database table has the following data after insertion:

hibernate inheritance table per class

Hope you got an idea on how table per class inheritance strategy works in Hibernate framework.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *