Inheritance is an important concept in Java but there is no counterpart in Relational world. There are some solutions in relational world but they are not widely used and also vendor specific. Hibernate provide some strategies to handle this situation where the inheritance hierarchy is mapped into the relational world.
Let's have the java classes where we have a User class and Employee and Customer are inherited from it. The various strategies employed by hibernate to map this hierarchy to relational world is as follows:
- Single Table per class hierarchy – Single table has all properties of every class in the hierarchy.
- Table per concrete class – Each subclass has a table having all the properties of super class also.
- Table per subclass – Each class is mapped in its own table. There is separate table for super class and subclass.Table
Single Table per class hierarchy
In Single table per subclass, the union of all the properties from the inheritance hierarchy is mapped
to one table. As all the data goes in one table, a discriminator is used to differentiate between different type of data.
to one table. As all the data goes in one table, a discriminator is used to differentiate between different type of data.
User (Base class)
//DiscriminatorColumn - Tells about the type of data
//DiscriminatorValue - Is the data is representing User type, the value is "USER"
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DISCRIMINATOR", discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("USER")
@Table(name="BASIC_USER")
public class User{
protected long id;
protected String name;
Customer class
//There is no id column
@Entity
@DiscriminatorValue("CUST")
public class Customer extends User{
protected double creditLimit;
Employee class
//There is no id column
@Entity
@DiscriminatorValue("EMP")
public class Employee extends User{
protected String rank
...
}
The table structure would look like
Basic_User Table
ID
NAME
CREDIT_LIMIT
RANK
DISCRIMINATOR
Now the objects can be saved polymorphically
//User object referred by User type reference
User user = new User();
user.setName("Superman");
session.save(user);
Customer customer = new Customer();
customer.setName("Spiderman");
customer.setCreditLimit(1060);
//User reference pointing to Customer object
User user2 = customer;
session.save(user2);
Advantages of Single Table per class hierarchy
- Simplest to implement.
- Only one table to deal with.
- Performance wise better than all strategies because no joins or sub-selects need to be performed.
Disadvantages:
- Most of the column of table are nullable so the NOT NULL constraint cannot be applied.
- Tables are not normalized.
Table per concrete class
In this case let's say our User class is abstract and Customer and Employee are concrete classes. So the table structure that comes out is basically one table for Customer and one table for Employee. The data for User is duplicated in both the tables. The User entity in this case is
User
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class User{
protected long id;
protected String name;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
...
Customer class
@Entity
public class Customer extends User{
protected double creditLimit;
...
This strategy is not popular.
Advantage:
- Possible to define NOT NULL constraints on the table.
Disadvantage:
- Tables are not normalized.
- To support polymorphism either container has to do multiple trips to database or use SQL UNION kind of feature.
Table per Sub class
In this case all the classes are mapped to its own table. It's highly normalized but performance is not good.
User class
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="BASIC_USER")
public class User implements Serializable{
protected long id;
protected String name;
...
}
Employee class
@Entity
@PrimaryKeyJoinColumn(name="EMPLOYEE_ID")
public class Employee extends User{
protected String rank;
...
}
The derived class mapped table contain the foreign key which maps it to the base class. The table structure is as follows
Basic_User Table
ID
NAME
Employee table
EMPLOYEE_ID // Acts both as primary key and foreign key.
RANK
Advantage:
- Tables are normalized.
- Able to define NOT NULL constraint.
Disadvantage:
- Does not perform as well as SINGLE_TABLE strategy
A comparison of three strategies is as follows:
Criteria | Single Table | Table per subclass | Table per concrete class |
Table Support |
|
|
|
Discriminator Column | Present | Absent | Absent |
Retrieving data | simple SELECT. All data is in one table. Using discriminator type, individual types can be selected | Joins among table. For example fetching Customer will require a join on Customer and User table. If all user needs to be fetched than it will put a join for all three tables | Separate Select or Union Select |
Updating and Inserting | Single INSERT or UPDATE | Multiple. For User type one insert on User table. For Customer type one insert on User table and another on Customer table. | One insert or update for each subclass |
Which Strategy to Choose
Usually the choice is between Single Table and Table per subclass strategy. Table per concrete class is optional in JPA and may hinder your portability later on.They also represent the two ends of the spectrum. Some rule of thumbs are:
- If there are many properties common in the base class and very less uncommon properties in derived class. Go for Single table strategy. However make sure your database architect is comfortable with nullable constraint not put on the properties of derived class. If there are too many uncommon properties, you might want to go for table per subclass strategy.
- Another criteria is the amount of polymorphic queries you do. If most of the time you fetch Customer and Employees separately you can go for Table per subclass as this will involve join only on two tables a time. However if you have requirements where you fetch User polymorphically most of the time, Single table strategy will give better performance.
Inheriting From a non entity base class
Let's say we want to put Audit information in many tables. We can make Audit as a base class and make our entities class to inherit from Audit class
Audit class
@MappedSuperclass
public class Audit {
protected Date updateDate;
protected Date createDate;
@Temporal(TemporalType.TIMESTAMP)
public Date getCreateDate() {
return createDate;
}
...
}
User class
@Entity
@Table(name="BASIC_USER")
//If we want to overried the column name than AttributeOverride is required
@AttributeOverride(name="createDate",column=@Column(name="CREATE_DATE"))
public class User extends Audit{
...
}
Great post, thank you for sharing.
ReplyDeletegood post
ReplyDeleteVery good post. Thank you!
ReplyDeleteThanks
Delete