SQL Inheritance Using Hibernate
Various strategies for mapping inheritance using hibernate.
Introduction
The relational database stores its data in rows and columns. This data structure is not easily mapped to the Object-oriented format. For example, data in the format of Rows and Columns cannot be easily thought of in terms of inheritance. Think of how a relational database will implement the concept of a table extending another table. It will not be easy. This problem is called the Object-Relational mismatch.
This problem is solved by the Java Persistence API (JPA). The JPA is a specification for interfacing between relational data format and object-oriented format. It is the specification for Object-Relational Mapping (ORM). One of the most popular implementations of the JPA is called Hibernate. Hibernate is a rich framework for mapping between objects and relational data.
Hibernate as an implementation of the JPA solves the problems of Inheritance in relational data formats. Hibernate solves this problem in various ways which will be discussed in this article. The solution you decide to implement should be based on a proper understanding of the requirements of your system. Let us check out how Hibernate solves this problem.
We will use an online school's database of users as the base for this tutorial. The school has various users like Lecturers and Students. These users have different properties but also common ones like Id, Username, Password, Picture, Name, etc. These common properties are better inherited from a superclass. The outlines of the classes are below.
User Class
public class User {
public long id;
private String firstName;
private String lastName;
private byte[] pic;
private Role role;
private String username;
private String password;
// getters and setters
}
Next, we have the outlines of the sub classes.
Lecturer class
public class Lecturer extends User{
private String levelsTitle;
private String employeeId;
// getters and setters
}
Student class
public class Student extends User{
private String matricNo;
private String level;
// getters and setters
}
The four common methods used for inheritance mapping in Hibernate are:
Table Per Concrete Class Using Implicit Polymorphism
Table Per Concrete Class Using Unions
Table Per Class Hierarchy
Table Per Class Using Joins
1. Table Per Concrete Class Using Implicit Polymorphism
Hibernate implements this strategy by creating a table for each concrete class. The subject of concrete classes comes from the fact that when implementing this strategy, you have to make the superclass abstract. Concrete class in this sense refers to the non-abstract classes that extend the parent class e.g the Student class and the Lecturer class.
Hibernate implements this in such a way that the fields of each concrete subclass in addition to the fields of the abstract superclass are all represented as columns in a single table for each concrete class.
So for our example, a single table for the subclass Student also has columns for the User superclass.
This strategy is easy to implement, but it is not easy to get a primary key for all the subclasses. For example, if you have a service that works with all Users, it won't be easy to get a primary key that references all the subclasses. Using our example classes, in the database, there won't be any primary key for all users. This is because there is no table called User, and neither does the Lecturer table knows that it has a polymorphic relationship with the Student table.
To implement this strategy, the superclass is to be abstracted and annotated with a @MappedSuperClass annotation. The subclasses extend the class as normal but with the id field. The subclasses are annotated with @Entity.
User Class
@MappedSuperClass
public abstract class User {
private String firstName;
private String lastName;
private byte[] pic;
private Role role;
private String username;
private String password;
//getters and setters
}
From the above listing, you will notice that the id field is no more found in the User class. This is because there is no single table that contains all the User classes. This is because all sub-class of the User class will be mapped to different tables. Meaning that all mapped sub-class of the User class will have a table to their name without any table for the User class.
As a result of this, you cannot have a single primary key that crosses all sub-classes nor is there any straightforward way to retrieve all users in a query.
For the subclasses, all you have to do is extend the User class and add the @Entity annotation to your class. There is an example below.
Lecturer Class
@Entity
public class Lecturer extends User{
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
private String department;
private String levelsTitle;
private String employeeId;
// getters and setters
}
Student class
@Entity
public class Student extends User{
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
private String matricNo;
private String level;
// getters and setters
}
2. Table Per Concrete Class Using Unions
This might look like a "Table per Concrete Class with Implicit Polymorphism" when you check their schema. This is because, only the concrete classes have Tables in the database for themselves. So for our example, there will be a table for the Student class and the Lecturer class but not for the User superclass.
In a way to differentiate itself, this strategy gives you the advantage of creating primary key fields in the superclass. So each Table in the database, mapping the concrete classes will hold the primary key. This denotes that there will be duplicate fields of the primary key across subclasses. With this advantage of having a single primary key for the hierarchy, it is easy to treat the whole hierarchy as a collection of Users. This addition makes it easy to get the whole set of Users either a Lecturer or a Student.
To implement inheritance with a Table per Concrete using unions, you will add an @Entity annotation and a @Inheritance annotation with the value of strategy as `InheritanceType.TABLE_PER_CLASS`
User Class
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class User {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
private String firstName;
private String lastName;
private byte[] pic;
private Role role;
private String username;
private String password;
//getters and setters
}
The subclasses are implemented with the @Entity annotation and extend the User class. Because of the inheritance strategy we are using, there will not be an Id field in the subclasses of this.
Lecturer Class
@Entity
public class Lecturer extends User{
private String department;
private String levelsTitle;
private String employeeId;
// getters and setters
}
Student class
@Entity
public class Student extends User{
private String matricNo;
private String level;
// getters and setters
}
3. Table Per Class Hierarchy
This strategy works such that the whole class hierarchy is flattened into a single table. For each hierarchy of subclasses and superclasses, there is only one table to map it. This works in a way that all the properties of the base classes and superclasses throughout the hierarchy are mapped as columns of a single table.
For example, when using the table per class hierarchy, there is only a table that has columns for the properties of Lecturer, Student, and User. When a new row of Lecturer is to be saved, the superclass column, user, is filled, and those of the Lecturer class are also filled, but those of the Student class which is found on a different branch of the hierarchy are left null. This is because the Lecturer's class does not contain the properties of the student class.
This strategy is the best when it comes to performance. But this does not come without a cost, as there will surely be null columns, you have to declare your properties in your class definition to be nullable. This poses a threat to data integrity as you lose one of the methods of maintaining it.
The table per class hierarchy is implemented below.
User Class
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = CONCRETE_TYPE)
public abstract class User {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false)
private byte[] pic;
@Column(nullable = false)
private Role role;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
//getters and setters
}
Lecturer Class
@Entity
public class Lecturer extends User{
@Column(nullable = true)
private String department;
@Column(nullable = true)
private String levelsTitle;
@Column(nullable = true)
private String employeeId;
// getters and setters
}
Student class
@Entity
public class Student extends User{
@Column(nullable = true)
private String matricNo;
@Column(nullable = true)
private String level;
// getters and setters
}
4. Table Per Subclass Using Joins
This strategy works in a way that Hibernate creates a new table for all the tables in the hierarchy. Each class gets a table, both the superclass and the subclass. This strategy is different from those of Table Per Concrete in that their superclasses are declared abstract, which means the superclasses do not get a table as they are not concrete.
As for selecting data, Hibernate uses a Join clause in the select statement on the primary key. This is achieved by adding the primary key in each table representing each class in the hierarchy.
The table per class using joins is implemented below.
User class
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class User {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false)
private byte[] pic;
@Column(nullable = false)
private Role role;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
//getters and setters
}
Lecturer Class
@Entity
public class Lecturer extends User{
@Column(nullable = true)
private String department;
@Column(nullable = true)
private String levelsTitle;
@Column(nullable = true)
private String employeeId;
// getters and setters
}
Student class
@Entity
public class Student extends User{
@Column(nullable = true)
private String matricNo;
@Column(nullable = true)
private String level;
// getters and setters
}
Conclusion
These various strategies give you the advantage to implement Inheritance in your models so that hibernate will be able to map them correctly to your database. Try to get the strategy that suits your needs. God bless you.
cover designed by Rawpixel.com - Freepik.com