Hibernate Table Per Subclass Inheritance Model

0

In this article, I will show you an example of Hibernate table per subclass inheritance model.

In my previous article on hibernate inheritance model, I showed you example of Single table per class hierarchy strategy.

Below are my setup details:

  • I am using Maven – the build tool
  • We will be using MySql as our database.
  • Eclipse  as the IDE, version Luna 4.4.1.
  • Hibernate 4.0.1.Final

Inheritance strategies

  1. Single table per class hierarchy All the instances of a class hierarchy are stored in one table.
  2. Table per subclass one table per class. The superclass has a table and each subclass owns a table. The subclass table contains only the uninherited properties.
  3. Table per class Each table contains ll the properties of the concrete class and the properties inherited from its superclasses.

Dependencies

Here are the dependencies.

pom.xml:

<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.javarticles.spring.hibernate</groupId>
	<artifactId>hibernateExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.26</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.12</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.12</version>
		</dependency>
		<dependency>
			<groupId>javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.12.1.GA</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-tools</artifactId>
			<version>4.3.1.CR1</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.common</groupId>
			<artifactId>hibernate-commons-annotations</artifactId>
			<version>4.0.1.Final</version>
			<classifier>tests</classifier>
		</dependency>
 		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.0.1.Final</version>
		</dependency>		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-antrun-plugin</artifactId>
				<version>1.8</version>
				<configuration>
					<target>
						<property name="compile_classpath" refid="maven.compile.classpath" />
						<ant antfile="build.xml">
							<target name="schemaexportTarget" />
						</ant>
					</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<properties>
		<hibernate.version>5.0.0.CR1</hibernate.version>
	</properties>

</project>

Joined Sub-Class Inheritance Strategy

In table-per-subclass mapping strategy, the parent and each subclass gets its own table. The concrete class state is a mix of the sub-class table and the table of superclass. The inherited state is retrieved by joining with the table of the superclass. Since the sub-class has its own table, there is no need of discriminator column.

Each subclass must, however, declare a table column holding the object identifier. The primary key of this table is also a foreign key to the superclass table and described by the @PrimaryKeyJoinColumns or the element <key column="desktop_id"/>.

Single Table Inheritance Model using JPA Annotations

We can map the single table inheritance model using JPA annotations. Few pints to note about:

  1. You specify the inheritance strategy using @Inheritance annotation
  2. The strategy attribute contains the InheritanceType. In our case it is InheritanceType.JOINED.
  3. The primary key of this table is also a foreign key to the superclass table and described by the @PrimaryKeyJoinColumns

Inheritance Model Example

Our example consists of an abstract class that represents hardware devices called Device. Desktop and Printer are concrete devices that extends Device. Both sub-classes have some additional properties apart from the inherited properties from Class Desktop.

Device Model

Device Model

Device:

package com.javarticles.hibernate;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name="devices")
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Device {
    @Id
    @GeneratedValue
    private Long deviceId;
    @Column(name="name", length=12, unique=true)    
    private String deviceName;
    @Column(name="device_address", length=80)   
    private String deviceAddress;

    public Long getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(Long deviceId) {
        this.deviceId = deviceId;
    }

    public String getDeviceName() {
        return deviceName;
    }

    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }

    public String getDeviceAddress() {
        return deviceAddress;
    }

    public void setDeviceAddress(String deviceAddress) {
        this.deviceAddress = deviceAddress;
    }

}

Desktop:

package com.javarticles.hibernate;

import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="desktop")
@PrimaryKeyJoinColumn(name="desktop_id")
public class Desktop extends Device {
    @ManyToOne
    @JoinColumn(name="printer")
    private Printer desktopPrinter;

    public Printer getDesktopPrinter() {
        return desktopPrinter;
    }

    public void setDesktopPrinter(Printer printer) {
        this.desktopPrinter = printer;
    }

    public String toString() {
        return "Desktop (" + getDeviceName() + "-" + getDeviceAddress() + "-"
                + getDesktopPrinter() + ")";
    }
}

Printer:

package com.javarticles.hibernate;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="printer")
@PrimaryKeyJoinColumn(name="printer_id")
public class Printer extends Device {
    @Column(name="printer_driver")
    private String printerDriver;
    @Column(name="queue_name")
    private String printerQueueName;

    public String getPrinterDriver() {
        return printerDriver;
    }

    public void setPrinterDriver(String printerDriver) {
        this.printerDriver = printerDriver;
    }

    public String getPrinterQueueName() {
        return printerQueueName;
    }

    public void setPrinterQueueName(String printerQueueName) {
        this.printerQueueName = printerQueueName;
    }

    public String toString() {
        return "Printer (" + getDeviceName() + "-" + getDeviceAddress() + "-"
                + getPrinterQueueName() + ")";
    }
}

ORM in XML for table per sub-class

We will now do the mapping for the table per sub-class strategy. Few things to note about the mapping:

  1. There is no discriminator element in the superclass mapping as we each subclass has its own table
  2. The subclass mappinh has table attribute, specify the name of the subclass table
  3. Use <joined-subclass> element to define the subclass mapping.
  4. We need to make the primary key of the subclass inherit the superclass value. Use the <key> element to declare the primary key / foreign key column.

devices.hbm.xml:

<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.javarticles.hibernate">
	<class name="com.javarticles.hibernate.Device" table="devices">
		<id name="deviceId" column="id" type="java.lang.Long" unsaved-value="null">
			<generator class="native">
				<param name="native">device_id</param>
			</generator>
		</id>
		<property name="deviceName" type="java.lang.String" column="name" length="12" not-null="true" unique-key="dev_name_unique_key" />
		<property name="deviceAddress" type="java.lang.String" column="device_address" length="80" />
	</class>

</hibernate-mapping>

desktop.hbm.xml:

<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.javarticles.hibernate">

	<joined-subclass name="com.javarticles.hibernate.Desktop"
		extends="com.javarticles.hibernate.Device"
		table="desktop">
		<key column="desktop_id"/>
		<many-to-one name="desktopPrinter" column="printer" class="com.javarticles.hibernate.Printer" not-null="false"/>
	</joined-subclass>

</hibernate-mapping>

printer.hbm.xml:

<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.javarticles.hibernate">

	<joined-subclass name="com.javarticles.hibernate.Printer"
		extends="com.javarticles.hibernate.Device"
		table="printer">
		<key column="printer_id"/>
		<property name="printerDriver"
			type="java.lang.String"
			column="printer_driver" length="30"/>
		<property name="printerQueueName" type="java.lang.String"
			column="queue_name" length="45" />
	</joined-subclass>

</hibernate-mapping>

Hibernate Configuration

hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.password">mnrpass</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/test</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hbm2ddl.auto">create-drop</property>
		<property name="show_sql">true</property>
		<property name="current_session_context_class">thread</property>
		<mapping resource="devices.hbm.xml" />
		<mapping resource="desktop.hbm.xml" />
		<mapping resource="printer.hbm.xml" />
	</session-factory>
</hibernate-configuration>

Let’s test it out by creating a Printer and Desktop object.

HibernateTablePerSubClassExample:

package com.javarticles.hibernate;

import java.io.IOException;

import org.hibernate.MappingException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class HibernateTablePerSubClassExample {

    public static void main(String[] args) throws MappingException, IOException {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        try {
            Session session = sessionFactory.getCurrentSession();
            Transaction tx = session.getTransaction();
            tx.begin();
            Printer printer = new Printer();
            printer.setDeviceAddress("192.168.1.252");
            printer.setPrinterDriver("HP Deskjet 1050");
            printer.setPrinterQueueName("PRNQ01");
            printer.setDeviceName("PRN01");
            session.save(printer);
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            printer = session.load(Printer.class, printer.getDeviceId());
            Desktop desktop = new Desktop();
            desktop.setDeviceAddress("127.0.0.1");
            desktop.setDeviceName("DESK01");
            desktop.setDesktopPrinter(printer);
            session.save(desktop);
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            Query query = session
                    .createQuery("from Desktop where deviceName='DESK01'");
            System.out.println("List all desktops: " + query.list());
            tx.commit();
        } finally {
            sessionFactory.close();
        }
    }
}

Output:

Hibernate: insert into devices (name, device_address) values (?, ?)
Hibernate: insert into printer (printer_driver, queue_name, printer_id) values (?, ?, ?)
Hibernate: insert into devices (name, device_address) values (?, ?)
Hibernate: insert into desktop (printer, desktop_id) values (?, ?)
22:34| INFO | QueryTranslatorFactoryInitiator.java 47 | HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select desktop0_.desktop_id as id1_1_, desktop0_1_.name as name2_1_, desktop0_1_.device_address as device_a3_1_, desktop0_.printer as printer2_0_ from desktop desktop0_ inner join devices desktop0_1_ on desktop0_.desktop_id=desktop0_1_.id where desktop0_1_.name='DESK01'
Hibernate: select printer0_.printer_id as id1_1_0_, printer0_1_.name as name2_1_0_, printer0_1_.device_address as device_a3_1_0_, printer0_.printer_driver as printer_2_2_0_, printer0_.queue_name as queue_na3_2_0_ from printer printer0_ inner join devices printer0_1_ on printer0_.printer_id=printer0_1_.id where printer0_.printer_id=?
List all desktops: [Desktop (DESK01-127.0.0.1-Printer (PRN01-192.168.1.252-PRNQ01))]
22:34| INFO | DriverManagerConnectionProviderImpl.java 264 | HHH000030: Cleaning up connection pool [jdbc:mysql://localhost/test]

Each sub-class gets its own table.

Schema Created:

Hibernate: create table desktop (desktop_id bigint not null, printer bigint, primary key (desktop_id))
Hibernate: create table devices (id bigint not null auto_increment, name varchar(12) not null, device_address varchar(80), primary key (id))
Hibernate: create table printer (printer_id bigint not null, printer_driver varchar(30), queue_name varchar(45), primary key (printer_id))
Hibernate: alter table desktop add constraint FKocldskry1h36mk5em9mjlmfwo foreign key (desktop_id) references devices (id)
Hibernate: alter table desktop add constraint FK3un741tl2wxd70e5d7dl7laci foreign key (printer) references printer (printer_id)
Hibernate: alter table devices add constraint dev_name_unique_key  unique (name)
Hibernate: alter table printer add constraint FKqc01oui187t8vx9fh38wbq6tx foreign key (printer_id) references devices (id)

As you can see below, desktop and printer inherits the primary key value from the devices table. The primary key columns of sub-class tables also acts as the foreign key, joining the sub-class table and the superclass table.

Data Created:

mysql> select * from devices;
+----+--------+----------------+
| id | name   | device_address |
+----+--------+----------------+
|  1 | PRN01  | 192.168.1.252  |
|  2 | DESK01 | 127.0.0.1      |
+----+--------+----------------+

mysql> select * from desktop;
+------------+---------+
| desktop_id | printer |
+------------+---------+
|          2 |       1 |
+------------+---------+
1 row in set (0.00 sec)

mysql> select * from printer;
+------------+-----------------+------------+
| printer_id | printer_driver  | queue_name |
+------------+-----------------+------------+
|          1 | HP Deskjet 1050 | PRNQ01     |
+------------+-----------------+------------+

Download the source code

This was an example about hibernate joined sub-class inheritance strategy.

hibernateTablePerSubClassExample.zip
Share.

Comments are closed.