Search This Blog

Sunday, 9 October 2011

The Hibernate Inheritance Mechanism - 3 + 4

We saw in approach 3 that a single table is used to represent an entire hierarchy. The end result was a table with sets of unrelated columns, absence of nullability constraints and constantly changing column count on addition of new sub-classes.
In approach 4 we saw how the 3 class hierarchy was split into 3 tables with each class being given a table. Although the resultant schema was a highly normalized schema,  it required joins for fetching records of any of the concrete class and was slightly more complex to implement compared to earlier approaches.
Here we shall modify approach 3 to use approach 4 techniques to come up with a slightly more cleaner implementation for approach 3.
In this technique the table representing the sportsmen hierarchy is used hold data for only one sub-class (in this case cricketer). A separate table is used to hold the properties specific to each of the other sub-classes(footballer in this case). The common properties including the Id is still retrieved from the common table. The mapping file will make things clearer
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
<hibernate-mapping package="com.inheritance.model">
    <class name="SportsPerson" table="SPORTS_PERSON">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        <discriminator column="SPORT_TYPE" type="string" />

        <property name="name" type="string" column="NAME" />

        <subclass name="Cricketer" discriminator-value="cricket">
            <property name="runs" type="integer" column="RUNS" />
            <property name="wickets" type="integer" column="WICKETS" />
            <property name="t20Player" type="boolean" column="T20_PLAYER" />

        <subclass name="Footballer" discriminator-value="footballer">
            <join table="FOOTBALLER">
                <key column="FOOTBALLER_ID" />
                <property name="goals" type="integer" column="GOALS" />
                <property name="appearances" type="integer" column="APPEARANCES" />
                <property name="sendOffs" type="integer" column="SEND_OFFS" />

In above mapping the element representing a cricketer has not been modified. However a join element has been used to indicate that the Footballer properties are placed in a separate table. The class does not have any id, hence no <id> element is present here. Instead a key element has been placed with a column attribute(like Technique 4). This field is used to hold the primary key of Sports_Person table. Thus the common properties of footballer are present in the base class and the footballer specific properties can be obtained by applying a join on this table for the id of footballer. Any constraints necessary can be placed directly on the footballer table. 
This technique can be used to enforce constraints on specific sub-classes while the remaining data can be persisted in SPORTS_PERSON table. 
On start up the logs display the new schema created:
2203 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table FOOTBALLER (
        FOOTBALLER_ID bigint not null,
        GOALS integer,
        APPEARANCES integer,
        SEND_OFFS integer,
        primary key (FOOTBALLER_ID)
2203 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table SPORTS_PERSON (
        ID bigint not null auto_increment,
        SPORT_TYPE varchar(255) not null,
        NAME varchar(255),
        RUNS integer,
        WICKETS integer,
        T20_PLAYER bit,
        primary key (ID)
2203 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    alter table FOOTBALLER 
        add index FK6B92FCDA24F34570 (FOOTBALLER_ID), 
        add constraint FK6B92FCDA24F34570 
        foreign key (FOOTBALLER_ID) 
        references SPORTS_PERSON (ID)
2250 [main] INFO  org.hibernate.tool.hbm2ddl.SchemaExport  - schema export compl

If we execute the earlier code to create a new footballer then the logs are as follows:
6688 [main] DEBUG org.hibernate.SQL  - 
        (NAME, SPORT_TYPE) 
        (?, 'footballer')
6735 [main] DEBUG  - Natively genera
ted identity: 1
750 [main] DEBUG org.hibernate.SQL  - 
        (?, ?, ?, ?)
The records where inserted in the two tables with null values for the cricketer related columns being set in SPORTS_PERSON. (This also means that it is not possible to apply nullabilty constraints on the Cricketer columns). Also if we execute a query to return the now created footballer by his name

Query query = session.createQuery("from Footballer f where like 'Tom%'");
The SQL query fired in this scenario uses an inner join to get the details from the second table.
        footballer0_.ID as ID0_,
        footballer0_.NAME as NAME0_,
        footballer0_1_.GOALS as GOALS1_,
        footballer0_1_.APPEARANCES as APPEARAN3_1_,
        footballer0_1_.SEND_OFFS as SEND4_1_ 
        SPORTS_PERSON footballer0_ 
    inner join
        FOOTBALLER footballer0_1_ 
            on footballer0_.ID=footballer0_1_.FOOTBALLER_ID 
        and (
            footballer0_.NAME like 'Tom%'
As can be seen, all the sub-classes other than cricket will involve the join query.
The pros of the approach are:
  1. The base table now holds the properties of one of the subclasses. So for one of the subclasses the join related performance issues is avoided.
  2. The mechanism retains the extensibility of Table per sub-class. If the hierarchy grows we simply need  to add a new table without affecting the existing code.
  3. Nullability constraints can be applied for the subclasses associated with separate tables.
The cons of the approach are:
  1. As details of cricket reside in the main table, no constraints can be applied on its columns that would not be met by other sub-classes.
  2. The performance issue is avoided only for one subclass.
  3. The discriminator column is not visible in the code. It is only available for Hibernate.

Update: For a brief summary of the Hibernate Inheritance types check this post.

1 comment: