Table Per Hierarchy with XML


Let us understand about Table Per Hierarchy with XML


In this approach, as the name suggests the entire hierarchy is mapped to a single table. I.e. All attributes of all the classes in the hierarchy are stored in a single table.

A discriminator column is used to distinguish different classes.

Null values will be stored in the table for which there is no column applicable.

Let’s consider the below example



Hibernate_InheritancePerHierarchy

In this example, we have 3 classes where Employee class is the super class for both PermanentEmployee and ContractEmployee classes but we will store all the attributes of all 3 classes in a single table.

Let’s create hibernate project using this strategy

Step 1

Create hibernate project

Please refer Hibernate setup in eclipse article on how to do it.

Project Structure


TPH_XML_Proj_structure

Step 2

Update pom.xml with Hibernate and Mysql dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<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>TablePerHierarchyXML</groupId>
    <artifactId>TablePerHierarchyXML</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>TablePerHierarchyXML</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
 
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.2.6.Final</version>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.5</version>
        </dependency>
 
    </dependencies>
</project>
<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>TablePerHierarchyXML</groupId>
	<artifactId>TablePerHierarchyXML</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>TablePerHierarchyXML</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>5.2.6.Final</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>6.0.5</version>
		</dependency>

	</dependencies>
</project>

Step 3

Create Employee class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kb.model;
 
public class Employee {
    private int id;
    private String name;
    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;
    }
}
package com.kb.model;

public class Employee {
	private int id;
	private String name;
	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;
	}
}

Step 4

Create PermanentEmployee class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kb.model;
 
public class PermanentEmployee extends Employee{
    private double salary;
 
    public double getSalary() {
        return salary;
    }
 
    public void setSalary(double salary) {
        this.salary = salary;
    }
 
}
package com.kb.model;

public class PermanentEmployee extends Employee{
	private double salary;

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

}

Step 5

Create ContractEmployee class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kb.model;
 
public class ContractEmployee extends Employee{
    private double hourlyRate;
 
    public double getHourlyRate() {
        return hourlyRate;
    }
 
    public void setHourlyRate(double hourlyRate) {
        this.hourlyRate = hourlyRate;
    }
 
}
package com.kb.model;

public class ContractEmployee extends Employee{
	private double hourlyRate;

	public double getHourlyRate() {
		return hourlyRate;
	}

	public void setHourlyRate(double hourlyRate) {
		this.hourlyRate = hourlyRate;
	}

}

Step 6

Create Employee.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version='1.0' encoding='UTF-8'?>  
<!DOCTYPE hibernate-mapping PUBLIC  
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  
<hibernate-mapping package="com.kb.model">  
 
<class name="Employee" table="Employee1" discriminator-value="E">  
<id name="id">  
       <generator class="increment"></generator>  
</id>  
  
<discriminator column="discriminator" type="string"></discriminator>  
<property name="name"></property>  
            
             <subclass name="PermanentEmployee" extends="Employee" discriminator-value="PE">  
                    <property name="salary"></property>  
             </subclass> 
            
             <subclass name="ContractEmployee" extends="Employee" discriminator-value="CE">  
                    <property name=" hourlyRate"></property>  
             </subclass>  
            
</class>  
            
</hibernate-mapping>  
<?xml version='1.0' encoding='UTF-8'?>  
<!DOCTYPE hibernate-mapping PUBLIC  
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  
<hibernate-mapping package="com.kb.model">  

<class name="Employee" table="Employee1" discriminator-value="E">  
<id name="id">  
       <generator class="increment"></generator>  
</id>  
  
<discriminator column="discriminator" type="string"></discriminator>  
<property name="name"></property>  
            
             <subclass name="PermanentEmployee" extends="Employee" discriminator-value="PE">  
                    <property name="salary"></property>  
             </subclass> 
            
             <subclass name="ContractEmployee" extends="Employee" discriminator-value="CE">  
                    <property name=" hourlyRate"></property>  
             </subclass>  
            
</class>  
            
</hibernate-mapping>  


We have defined only one mapping file for all 3 classes.

“discriminator” tag is used to define the discriminator column which is mainly used to identify the type of the record.

We have defined discriminators as “E” to identify that record belongs to Employee class

PE” to identify that record belongs to PermanentEmployee class

CE” to identify that record belongs to ContractEmployee class

Hibernate will put the apprpariate discriminator values while persisting the record based on what record we are persisting.

subclass” tag is used to map the subclasses.

Generally we use “class” tag to map the class with table in mapping file but, if the class itself is a subclass then we use “subclass” tag.

We used “subclass” tag for both PermanentEmployee and ContractEmployee classes as both are subclasses of Employee class.

Step 7

Create hibernate.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
       "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
       "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
 
   <session-factory>
 
       <!-- Database connection properties -->
       <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
       <property name="connection.url">jdbc:mysql://localhost/javainsimpleway</property>
       <property name="connection.username">root</property>
       <property name="connection.password">root</property>
 
       <!-- JDBC connection pool (using the built-in) -->
       <property name="connection.pool_size">1</property>
 
       <!-- SQL dialect -->
       <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
 
       <!-- Disable the second-level cache -->
       <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
 
       <!-- Echo all executed SQL to stdout -->
       <property name="show_sql">true</property>
       
       <!-- Format the generated Sql -->
       <property name="format_sql">true</property>
 
       <!-- Dont Drop and re-create the database schema on startup,Just update it -->
       <property name="hbm2ddl.auto">update</property>
 
       <mapping resource="com/kb/mapping/employee.hbm.xml"/>
 
   </session-factory>
 
</hibernate-configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
       "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
       "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
 
   <session-factory>
 
       <!-- Database connection properties -->
       <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
       <property name="connection.url">jdbc:mysql://localhost/javainsimpleway</property>
       <property name="connection.username">root</property>
       <property name="connection.password">root</property>
 
       <!-- JDBC connection pool (using the built-in) -->
       <property name="connection.pool_size">1</property>
 
       <!-- SQL dialect -->
       <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
 
       <!-- Disable the second-level cache -->
       <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
 
       <!-- Echo all executed SQL to stdout -->
       <property name="show_sql">true</property>
       
       <!-- Format the generated Sql -->
       <property name="format_sql">true</property>
 
       <!-- Dont Drop and re-create the database schema on startup,Just update it -->
       <property name="hbm2ddl.auto">update</property>
 
       <mapping resource="com/kb/mapping/employee.hbm.xml"/>
 
   </session-factory>
 
</hibernate-configuration>


We have defined all the database configuration in this file

hbm2ddl.auto property is defined in the config file which helps in automatic creation of tables in the database based on the mapping.

We have also provided the mapping xml file location using “mapping” tag.

Step 8

Create Hibernate util class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.kb.util;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
    private static final SessionFactory sessionFactory = buildSessionFactory();
 
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception to track it
            System.err.println("SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
 
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static void shutdown() {
        // Optional but can be used to Close caches and connection pools
        getSessionFactory().close();
    }
 
}
package com.kb.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
	private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception to track it
            System.err.println("SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static void shutdown() {
    	// Optional but can be used to Close caches and connection pools
    	getSessionFactory().close();
    }

}

Step 9

Create main class to interact with DB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.kb.db;
 
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
 
import com.kb.model.ContractEmployee;
import com.kb.model.Employee;
import com.kb.model.PermanentEmployee;
import com.kb.util.HibernateUtil;
 
public class Main {
    public static void main(String[] args) {
        //Get session factory using Hibernate Util class
        SessionFactory sf = HibernateUtil.getSessionFactory();
        //Get session from Sesson factory
        Session session = sf.openSession();
        
        //Begin transaction
        Transaction t=session.beginTransaction();  
 
        //Creating Employee base class record
        Employee employee=new Employee();  
        employee.setName("John");  
        
        //Creating Permanent Employee subclass record
        PermanentEmployee permanentEmployee=new PermanentEmployee();  
        permanentEmployee.setName("Jacob");  
        permanentEmployee.setSalary(30000);  
        
       //Creating Contract Employee subclass record
        ContractEmployee contractEmployee=new ContractEmployee();  
        contractEmployee.setName("Raj");  
        contractEmployee.setHourlyRate(2000);  
        
        //persist all the employee records
        session.persist(employee);  
        session.persist(permanentEmployee);  
        session.persist(contractEmployee);  
        
        //Commit the transaction and close the session
        t.commit();  
        session.close();  
        System.out.println("successfully persisted all the Employee records");  
    }
 
}
package com.kb.db;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.kb.model.ContractEmployee;
import com.kb.model.Employee;
import com.kb.model.PermanentEmployee;
import com.kb.util.HibernateUtil;

public class Main {
	public static void main(String[] args) {
		//Get session factory using Hibernate Util class
		SessionFactory sf = HibernateUtil.getSessionFactory();
		//Get session from Sesson factory
		Session session = sf.openSession();
		
		//Begin transaction
		Transaction t=session.beginTransaction();  

		//Creating Employee base class record
		Employee employee=new Employee();  
		employee.setName("John");  
	    
		//Creating Permanent Employee subclass record
	    PermanentEmployee permanentEmployee=new PermanentEmployee();  
	    permanentEmployee.setName("Jacob");  
	    permanentEmployee.setSalary(30000);  
	    
	   //Creating Contract Employee subclass record
	    ContractEmployee contractEmployee=new ContractEmployee();  
	    contractEmployee.setName("Raj");  
	    contractEmployee.setHourlyRate(2000);  
	    
	    //persist all the employee records
	    session.persist(employee);  
	    session.persist(permanentEmployee);  
	    session.persist(contractEmployee);  
	    
	    //Commit the transaction and close the session
	    t.commit();  
	    session.close();  
	    System.out.println("successfully persisted all the Employee records");  
	}

}


We have created 3 records, one for each Employee, PermanentEmployee and ContractEmployee class.

When we persist these 3 records of different objects, It will be saved in a Single table.

Step 10

Run the above class to check the output

Hibernate: 
    
    create table Employee1 (

        id integer not null,

        discriminator varchar(255) not null,

        name varchar(255),

        salary double precision,

        hourlyRate double precision,

        primary key (id)
    )


Hibernate: 

    select
        max(id)  

    from
        Employee1


Hibernate: 

    insert 
    into
        Employee1
        (name, discriminator, id) 
    values
        (?, 'E', ?)


Hibernate: 

    insert 
    into
        Employee1
        (name, salary, discriminator, id) 
    values
        (?, ?, 'PE', ?)


Hibernate: 

    insert 
    into
        Employee1
        (name, hourlyRate, discriminator, id) 
    values
        (?, ?, 'CE', ?)

successfully persisted all the Employee records


We can see that Create statement is executed only once as it creates a Single table for all the classes.

3 insert statements one for each object we persisted.

Check Table in MYSQL workbench

SELECT * FROM javainsimpleway.employee1;

TPH_Output1

Check Table in MYSQL console

E:\MySql_Install\bin
Mysql –u root –p
Enter password

SELECT * FROM javainsimpleway.employee1;

TPH_Output2

We can see NULL values are stored in Salary column for Contract employee and NULL values are stored in HourlyRate column for Permanent employees.

We can see NULL values are stored in salary and hourlyRate columns for Employee record.

We can see that discriminator column can be used to identify the type of the record.

Advantage

This hierarchy provides best performance when the hierarchy depth is high like multi level as there is only single select query is executed.

Since there are no joins or sub selects are involved, its performance will be good.

Very simple to implement.

Disadvantage

Tables will have unnecessary columns for some of the records as all the columns are not applicable for each record.

If most of the column values are NULL then it is difficult to put a constraint.

Note :This hierarchy is not normalized as many irrelevant columns will be present in each record.

Download this project TablePerHierarchyXML.zip

About the Author

Karibasappa G C (KB)
Founder of javainsimpleway.com
I love Java and open source technologies and very much passionate about software development.
I like to share my knowledge with others especially on technology 🙂
I have given all the examples as simple as possible to understand for the beginners.
All the code posted on my blog is developed,compiled and tested in my development environment.
If you find any mistakes or bugs, Please drop an email to kb.knowledge.sharing@gmail.com

Connect with me on Facebook for more updates

Share this article on