Hibernate Single Table Inheritance Model

0

Object relational mapping is all about mapping an object model to its relational model. In the object model, inheritance is one of the most common concept where one or more classes extends a base class. How does the database model looks like?

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.

Single Table Inheritance Model

In this article, we will see how to map entities using the table per class hierarchy strategy.
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;


public abstract class Device {
    private Long deviceId; 
    private String deviceName;
    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;


public class Desktop extends Device {
    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;


public class Printer extends Device {
    private String printerDriver;
    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 single table per class

In single table per class hierarchy strategy, the properties of all the subclasses in a given mapped class hierarchy are stored in a single table. Though the instance of classes reside in the same table, each subclass can only access its own declared properties and the properties of its subclasses. It can also access the properties from the inherited root class. In other words, a class can access only those properties that the inherited java model allows.
In our device model, a Printer object can access its own properties as well as properties of its parent the Device. Likewise a Desktop object can access its own members as well as the super class’s Device members.
Each subclass in a hierarchy must define a unique discriminator value so hibernate can filter out the data based on the concrete class. If this is not specified, the fully qualified Java class name is used.
<discriminator> element is used to specify the discriminator column. The discriminator value is set in the subclasses using attribute discriminator-value.
Let’s look at at the ORM in XML.
In hbm.xml, for the table-per-class-hierarchy mapping strategy, the <subclass> declaration is used.

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>

		<discriminator column="device_type" not-null="false" type="string" length="10" />
		<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">

	<subclass name="com.javarticles.hibernate.Desktop"
		extends="com.javarticles.hibernate.Device"
		discriminator-value="DESKTOP">
		<many-to-one name="desktopPrinter" column="printer" class="com.javarticles.hibernate.Printer" not-null="false"/>
	</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">

	<subclass name="com.javarticles.hibernate.Printer"
		extends="com.javarticles.hibernate.Device"
		discriminator-value="PRINTER">

		<property name="printerDriver"
			type="java.lang.String"
			column="printer_driver" length="30"/>
		<property name="printerQueueName" type="java.lang.String"
			column="queue_name" length="45" />
	</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.

HibernateSingleTablePerClassExample:

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

    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: drop table if exists devices
Hibernate: create table devices (id bigint not null auto_increment, device_type varchar(10), name varchar(12) not null, device_address varchar(80), printer bigint, printer_driver varchar(30), queue_name varchar(45), primary key (id))
Hibernate: alter table devices add constraint dev_name_unique_key  unique (name)
Hibernate: alter table devices add constraint FK75mlql13vsy1430pq2q29c450 foreign key (printer) references devices (id)
11:33| INFO | SchemaExport.java 458 | HHH000230: Schema export complete
Hibernate: insert into devices (name, device_address, printer_driver, queue_name, device_type) values (?, ?, ?, ?, 'PRINTER')
Hibernate: insert into devices (name, device_address, printer, device_type) values (?, ?, ?, 'DESKTOP')
11:33| INFO | QueryTranslatorFactoryInitiator.java 47 | HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select desktop0_.id as id1_0_, desktop0_.name as name3_0_, desktop0_.device_address as device_a4_0_, desktop0_.printer as printer5_0_ from devices desktop0_ where desktop0_.device_type='DESKTOP' and desktop0_.name='DESK01'
Hibernate: select printer0_.id as id1_0_0_, printer0_.name as name3_0_0_, printer0_.device_address as device_a4_0_0_, printer0_.printer_driver as printer_6_0_0_, printer0_.queue_name as queue_na7_0_0_ from devices printer0_ where printer0_.id=? and printer0_.device_type='PRINTER'
List all desktops: [Desktop (DESK01-127.0.0.1-Printer (PRN01-192.168.1.252-PRNQ01))]

devices table data:

--+---------+
| device_type | deviceId | device_address | name   | printer_driver  | queue_nam
e | printer |
+-------------+----------+----------------+--------+-----------------+----------
--+---------+
| PRINTER     |        1 | 192.168.1.252  | PRN01  | HP Deskjet 1050 | PRNQ01
  |    NULL |
| DESKTOP     |        2 | 127.0.0.1      | DESK01 | NULL            | NULL
  |       1 |
+-------------+----------+----------------+--------+-----------------+----------

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.SINGLE_TABLE.
  3. Define the discriminator column using attribute @DiscriminatorColumn
  4. Define the discriminator value using @DiscriminatorValue, the value will be specified as the annotation’s value, for example @DiscriminatorColumn(name="device_type")

Device:

package com.javarticles.hibernate;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
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.SINGLE_TABLE)
@DiscriminatorColumn(name="device_type")
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.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="desktop")
@DiscriminatorValue("DESKTOP")
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.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name="printer")
@DiscriminatorValue("PRINTER")
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() + ")";
    }
}

The mapping classes containing the annotations must be mentioned in the hibernate configuration file so that they can be scanned to build the OR mapping.

annotaions/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 class="com.javarticles.hibernate.Device" />
		<mapping class="com.javarticles.hibernate.Desktop" />
		<mapping class="com.javarticles.hibernate.Printer" />
	</session-factory>
</hibernate-configuration>

The annotation specific configuration is located in annotation/hibernate.cfg.xml so that we can run the both the above examples separately.

HibernateSingleTablePerClassJpaExample:

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

    public static void main(String[] args) throws MappingException, IOException {
        Configuration configuration = new Configuration().configure("annotation/hibernate.cfg.xml");
        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();
        }
    }
}

Download the source code

This was an example about hibernate single table inheritance strategy.

You can download the source code here: hibernateSingleTablePerClassExample.zip
Share.

Comments are closed.