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
- Single table per class hierarchy All the instances of a class hierarchy are stored in one table.
- 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.
- 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:
- You specify the inheritance strategy using
@Inheritance
annotation - The
strategy
attribute contains theInheritanceType
. In our case it isInheritanceType.JOINED
. - 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:
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:
- There is no
discriminator
element in the superclass mapping as we each subclass has its own table - The subclass mappinh has
table
attribute, specify the name of the subclass table - Use
<joined-subclass>
element to define the subclass mapping. - 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.