11.1 Overview
We have seen mapping of persistent class in a table, but there is structural difference when we compare Objects with Relational Database. Java Object supports Inheritance where as Relational database does not.
Hibernate supports four approaches to address this gap.
- Table per Class Hierarchy
- Table per Subclass
- Table per Concrete Class with Implicit Polymorphism
- Table per Concrete Class with Union
This chapter will focus on the explanation of these approaches.
11.2 Table Per Class Hierarchy
As the name suggests, there will be a single table for the entire hierarchy. With this approach, all columns corresponding to the properties in subclasses and main class are included in the table.
As depending on the subclass, columns related to other subclass will be null so all the columns related to subclass properties cannot have not-null constraint.
To determine the type of subclass, discriminator column is created in the table. This column will not have any property in object hierarchy.
<subclass> tag is used to define the subclasses.
NOTE: Discriminator value will be based on implementation which means if the reference is of type parent class and implementation is of subclass then discriminator will be of subclass.
This approach provides simplicity with better performance.
Scenario - Lets take an example of Users of an application. Assume our application can have three types of users – student, accountant and professors.
User will be a base class with all common properties and Student, Professors are the subclasses
- User – this is a base class with id, name and email address properties.
- Student – this is a subclass with subject property.
- Professor- this is a subclass with salary property.
Implementation
User.java
package com.tutorial.hibernate; public class User { private int id; private String name; private String emailAddress; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } }
Student.java
package com.tutorial.hibernate; public class Student extends User { private String course; public String getCourse() { return course; } public void setCourse(String course) { this.course = course; } }
Professor.java
package com.tutorial.hibernate; public class Professor extends User { private int salary; public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } }
Define hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> property name="hibernate.connection.url"> jdbc:mysql://localhost:3306/tutorial </property> <property name="hibernate.connection.username"> root </property> <property name="hibernate.connection.password"> password </property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <mapping resource="table-per-hierarchy.hbm.xml" /> </session-factory> </hibernate-configuration>
Define table-per-hierarchy.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.tutorial.hibernate.User" table="USERS" discriminator-value="USER"> <id name="id" type="int" column="user_id"> <generator class="native"/> </id> <discriminator column="type" type="string"></discriminator> <property name="name" column="name" type="string"/> <property name="emailAddress" column="email_address" type="string" /> <subclass name="com.tutorial.hibernate.Student" discriminator-value="STUDENT"> <property name="course" column="course" type="string"></property> </subclass> <subclass name="com.tutorial.hibernate.Professor" discriminator-value="PROFESSOR"> <property name="salary" column="salary" type="int"></property> </subclass> </class> </hibernate-mapping>
Create Users table
create table USERS ( user_id integer not null auto_increment, type varchar(255) not null, name varchar(255), email_address varchar(255), course varchar(255), salary integer, primary key (user_id) )
Test.java
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.tutorial.hibernate.Book; import com.tutorial.hibernate.Professor; import com.tutorial.hibernate.Student; import com.tutorial.hibernate.User; public class Test { public static void main(String args[]) { Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session= factory.openSession(); Transaction tx = session.beginTransaction(); User user = new User(); user.setEmailAddress("user@univ.com"); user.setName("USER A"); Student student = new Student(); student.setEmailAddress("student@univ.com"); student.setName("STUDENT A"); student.setCourse("Computer Science"); Professor professor = new Professor(); professor.setEmailAddress("professor@univ.com"); professor.setName("PROFESSOR"); professor.setSalary(2000); session.save(user); session.save(student); session.save(professor); tx.commit(); session.close(); factory.close(); } }
Run the Test.java
11.2 Table Per Sub Class
In this approach for each and every class (abstract, interface etc ), there will be a separate table and a foreign key- primary key relationship between base and subclasses.
Instead of <subclass> , we will use <joined-subclass> tag.
Scenario - Lets define a Game base class with Indoor and Outdoor as subclasses .
Game.java –
package com.tutorial.hibernate; public class Game { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
IndoorGame.java
package com.tutorial.hibernate; public class IndoorGame extends Game { private int numberOfPlayers; private String ageGroup; public int getNumberOfPlayers() { return numberOfPlayers; } public void setNumberOfPlayers(int numberOfPlayers) { this.numberOfPlayers = numberOfPlayers; } public String getAgeGroup() { return ageGroup; } public void setAgeGroup(String ageGroup) { this.ageGroup = ageGroup; } }
OutdoorGame.java
package com.tutorial.hibernate; public class OutdoorGame extends Game { private String requiredArea; public String getRequiredArea() { return requiredArea; } public void setRequiredArea(String requiredArea) { this.requiredArea = requiredArea; } }
Table-per-subclass.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.tutorial.hibernate.Game" table="GAMES"> <id name="id" type="int" column="game_id"> <generator class="native"/> </id> <property name="name" column="name" type="string"/> <joined-subclass name="com.tutorial.hibernate.IndoorGame" table="INDOOR_GAME"> <key column="GAME_ID"></key> <property name="ageGroup" column="age_group" type="string"></property> <property name="numberOfPlayers" column="number_of_players" type="int"></property> </joined-subclass> <joined-subclass name="com.tutorial.hibernate.OutdoorGame" table="OUTDOOR_GAME"> <key column="GAME_ID"></key> <property name="requiredArea" column="area_required" type="string"></property> </joined-subclass> </class> </hibernate-mapping>
Create tables
CREATE TABLE 'games' ( 'game_id' int(11) NOT NULL AUTO_INCREMENT, 'name' varchar(255) DEFAULT NULL, PRIMARY KEY ('game_id') ) CREATE TABLE 'indoor_game' ( 'GAME_ID' int(11) NOT NULL, 'age_group' varchar(255) DEFAULT NULL, 'number_of_players' int(11) DEFAULT NULL, PRIMARY KEY ('GAME_ID'), CONSTRAINT 'FK_asd77s8ye1i34m9s2km5tslcx' FOREIGN KEY ('GAME_ID') REFERENCES 'games' ('game_id') ) CREATE TABLE 'outdoor_game' ( 'GAME_ID' int(11) NOT NULL, 'area_required' varchar(255) DEFAULT NULL, PRIMARY KEY ('GAME_ID'), CONSTRAINT 'FK_bp8vwf6q9yfq1b6xcrk5j4sc6' FOREIGN KEY ('GAME_ID') REFERENCES 'games' ('game_id') )
Test Program
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.tutorial.hibernate.Book; import com.tutorial.hibernate.OutdoorGame; import com.tutorial.hibernate.IndoorGame; import com.tutorial.hibernate.Game; public class Test { public static void main(String args[]) { Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session= factory.openSession(); Transaction tx = session.beginTransaction(); Game game = new Game(); game.setName("Main Game"); IndoorGame indoorGame = new IndoorGame(); indoorGame.setAgeGroup("2+"); indoorGame.setName("Clay Game"); indoorGame.setNumberOfPlayers(2); OutdoorGame outdoorGame = new OutdoorGame(); outdoorGame.setName("Cricket"); outdoorGame.setRequiredArea("22 yards"); session.save(game); session.save(indoorGame); session.save(outdoorGame); tx.commit(); session.close(); factory.close(); } }
Run the Test Program
Game Table
Indoor_game table
Outdoor_game table
11.4 Table Per Concrete Class with Implicit Polymorphism
In this approach there will be one table for all concrete (non abstract ) subclass and will contains the column of base class. Yes you are right – this approach has a redundant columns.
We need not to do anything special for this approach.
11.5 Table Per Concrete Class with Union
This approach works even when base class is abstract or interface (no separate table is needed for base class). If the base class is concrete , there will be table corresponding to the base table as well.
We need to specify abstract=true if base class is abstract or interface.
<union-subclass> tag is used to specify the sub classes.
NOTE: hibernate does not support “increment” identifier generation with this approach
Lets use the same scenario that we discussed in section 11.3. Change the Game class to abstract class as highlighted. IndoorGame.java and Outdoor.java , hibernate.cfg.xml file will remain same (refer section 11.3)
Game.java
package com.tutorial.hibernate; public abstract class Game { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Table-per-concrete.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.tutorial.hibernate.Game" abstract="true"> <id name="id" type="int" column="game_id"> <generator class="hilo"/> </id> <property name="name" column="name" type="string"/> <union-subclass name="com.tutorial.hibernate.IndoorGame" table="INDOOR_GAME"> <property name="ageGroup" column="age_group" type="string"></property> <property name="numberOfPlayers" column="number_of_players" type="int"></property> </union-subclass> <union-subclass name="com.tutorial.hibernate.OutdoorGame" table="OUTDOOR_GAME"> <property name="requiredArea" column="area_required" type="string"></property> </union-subclass> </class> </hibernate-mapping>
Create table . (As Game is abstract class , no table is needed for Game)
Indoor_game table
CREATE TABLE 'indoor_game' ( 'game_id' int(11) NOT NULL, 'name' varchar(255) DEFAULT NULL, 'age_group' varchar(255) DEFAULT NULL, 'number_of_players' int(11) DEFAULT NULL, PRIMARY KEY ('game_id') )
Outdoor_game table
CREATE TABLE 'outdoor_game' ( 'game_id' int(11) NOT NULL, 'name' varchar(255) DEFAULT NULL, 'area_required' varchar(255) DEFAULT NULL, PRIMARY KEY ('game_id') )
Test Program
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.tutorial.hibernate.Book; import com.tutorial.hibernate.OutdoorGame; import com.tutorial.hibernate.IndoorGame; import com.tutorial.hibernate.Game; public class Test { public static void main(String args[]) { Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session= factory.openSession(); Transaction tx = session.beginTransaction(); IndoorGame indoorGame = new IndoorGame(); indoorGame.setAgeGroup("2+"); indoorGame.setName("Clay Game"); indoorGame.setNumberOfPlayers(2); OutdoorGame outdoorGame = new OutdoorGame(); outdoorGame.setName("Cricket"); outdoorGame.setRequiredArea("22 yards"); session.save(indoorGame); session.save(outdoorGame); tx.commit(); session.close(); factory.close(); } }
Run the program
If the base class is not abstract then we need to use table attribute instead of abstract=true and table will be created. In this case since base class is not abstract then we can persist base data as well .
11.6 Key Points
Table Per Hierarchy –
- There is one table for entire hierarchy and one additional discriminator column.
- There will be no property in java object corresponding to discriminator column.
- <subclass> tag is used in hbm xml files.
- If most of the properties are common , then go for this approach.
Advantages
- This approach gives a best performance as one select statement is needed to pull the data. (no joins )
- Simple to implement as there will be only one table.
Disadvantages
- Cannot have not null constraints on columns related to subclass fields.
- Columns will have null data.
- Not normalized.
Table per Subclass-
- Each and every class (abstract, interface etc ) will have a separate table.
- Parent key- foreign key relationship between base and subclasses tables
- <joined-subclass> tag will be used.
- Common data will be stored in parent table.
- Tables are normalized and can apply not null constraints.
- Requires joins to fetch the data so comparatively slow performance as compared to Table per hierarchy. It will have much impact when level of inheritance increases.
- If there are very less common properties , go for this approach.
Table per Concrete Class with Union-
- Supports base class as abstract or interface (no separate table is needed for base class).
- Separate table If the base class is concrete
- We need to specify abstract=true if base class is abstract or interface.
- <union-subclass> tag is used to specify the sub classes.
- does not support “increment” identifier generation with this approach.
- Subclasses will have parent columns as well.
- Tables are not normalized.
- Optional in JPA.
Table Per Concrete Class with Implicit Polymorphism
- one table for all concrete (non abstract ) subclass and will contains the column of base class.
- this approach has a redundant columns.