Search This Blog

Thursday, 6 October 2011

The Hibernate Inheritance Mechanism - 4

We have seen three of the inheritance techniques as of yet.This is the fourth in the series.
Table per subclass
In this technique a table is created for every class in the hierarchy. There exists a separate table for every interface/abstract class/concrete class that has any persistent properties.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.inheritance.model">
    <class name="SportsPerson" table="SPORTS_PERSON">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        </id>        
        <property name="name" type="string" column="NAME" />

        <joined-subclass name="Cricketer" table="CRICKETER">
            <key column = "CRICKETER_ID" />
            <property name="runs" type="integer" column="RUNS" />
            <property name="wickets" type="integer" column="WICKETS" />
            <property name="t20Player" type="boolean" column="T20_PLAYER" />
        </joined-subclass>

        <joined-subclass name="Footballer" 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" />
        </joined-subclass>
    </class>
</hibernate-mapping>
On start up the SQL generated is as below:
922  [main] INFO  org.hibernate.cfg.HbmBinder  - Mapping class: com.inheritance.
model.SportsPerson -> SPORTS_PERSON
...
1062 [main] INFO  org.hibernate.cfg.HbmBinder  - Mapping joined-subclass: com.in
heritance.model.Cricketer -> CRICKETER
...
1078 [main] INFO  org.hibernate.cfg.HbmBinder  - Mapping joined-subclass: com.in
heritance.model.Footballer -> FOOTBALLER
2594 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Ver
sion select: select ID from SPORTS_PERSON where ID =?
2594 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Ins
ert 0: insert into SPORTS_PERSON (NAME, ID) values (?, ?)
2594 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Upd
ate 0: update SPORTS_PERSON set NAME=? where ID=?
2609 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Del
ete 0: delete from SPORTS_PERSON where ID=?
2625 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Sna
pshot select: select cricketer_.CRICKETER_ID, cricketer_1_.NAME as NAME0_, crick
eter_.RUNS as RUNS1_, cricketer_.WICKETS as WICKETS1_, cricketer_.T20_PLAYER as 
T4_1_ from CRICKETER cricketer_ inner join SPORTS_PERSON cricketer_1_ on cricket
er_.CRICKETER_ID=cricketer_1_.ID where cricketer_.CRICKETER_ID=?
2625 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Ins
ert 1: insert into CRICKETER (RUNS, WICKETS, T20_PLAYER, CRICKETER_ID) values (?
, ?, ?, ?)
2625 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Upd
ate 1: update CRICKETER set RUNS=?, WICKETS=?, T20_PLAYER=? where CRICKETER_ID=?
2625 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Del
ete 1: delete from CRICKETER where CRICKETER_ID=?
...[similar queries for footballer]
3094 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table CRICKETER (
        CRICKETER_ID bigint not null,
        RUNS integer,
        WICKETS integer,
        T20_PLAYER bit,
        primary key (CRICKETER_ID)
    )
3094 [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)
    )
3109 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table SPORTS_PERSON (
        ID bigint not null auto_increment,
        NAME varchar(255),
        primary key (ID)
    )
3109 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    alter table CRICKETER 
        add index FKA046883E7573CC6 (CRICKETER_ID), 
        add constraint FKA046883E7573CC6 
        foreign key (CRICKETER_ID) 
        references SPORTS_PERSON (ID)
3125 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    alter table FOOTBALLER 
        add index FK6B92FCDAEDEE02CA (FOOTBALLER_ID), 
        add constraint FK6B92FCDAEDEE02CA 
        foreign key (FOOTBALLER_ID) 
        references SPORTS_PERSON (ID)
3172 [main] INFO  org.hibernate.tool.hbm2ddl.SchemaExport  - schema export compl
ete

The above logs indicate the creation of 3 different tables.

As can be seen the common properties (including the identifier) are present in the SPORTS_PERSON table. The other tables hold foreign key relations to this primary key. 
Each table holds the properties specific to its own type thus ensuring a very normalized schema. It is now also possible to enforce nullabilty constraints as each sub class has its own table. Adding a new subclass is as simple as creating a new table with similar foreign key  relations. The existing code is not affected.
The queries in the logs indicate the use of inner joins to fetch the complete record for a Cricketer. Also the inserts involve creation of records in two tables. The performance in this approach is certainly not the best. For example the code to create a footballer is as below:

private static void createFootballer() {
    Footballer footballer = new Footballer();
    footballer.setAppearances(5);
    footballer.setName("Tom Cleaverly");
    footballer.setGoals(3);
    footballer.setSendOffs(0);
        
    Session session = sessionFactory.openSession();
    Transaction t = session.beginTransaction();
    session.save(footballer);
    t.commit();        
}
The logs generated for the creation is as below:
3906 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - executing
 identity-insert immediately
3906 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Inse
rting entity: com.inheritance.model.Footballer (native id)
3906 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
3922 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        SPORTS_PERSON
        (NAME) 
    values
        (?)
...
3938 [main] DEBUG org.hibernate.id.IdentifierGeneratorFactory  - Natively genera
ted identity: 1
3953 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Inse
rting entity: [com.inheritance.model.Footballer#1]
3953 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
3953 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        FOOTBALLER
        (GOALS, APPEARANCES, SEND_OFFS, FOOTBALLER_ID) 
    values
        (?, ?, ?, ?)
I also executed a query from the previous example

Query query = session.createQuery("from Footballer f where f.name like 'Tom%'");
The logs indicate the execution:
937 [main] DEBUG org.hibernate.SQL  - 
    select
        footballer0_.FOOTBALLER_ID as ID0_,
        footballer0_1_.NAME as NAME0_,
        footballer0_.GOALS as GOALS2_,
        footballer0_.APPEARANCES as APPEARAN3_2_,
        footballer0_.SEND_OFFS as SEND4_2_ 
    from
        FOOTBALLER footballer0_ 
    inner join
        SPORTS_PERSON footballer0_1_ 
            on footballer0_.FOOTBALLER_ID=footballer0_1_.ID 
    where
        footballer0_1_.NAME like 'Tom%'
Even a simple query to fetch a Footballer involved the join between two tables. As the complexity increases, there could be a considerable degradation of performance.

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

2 comments: