Introduction
Inheritance is a fundamental concept in object-oriented programming, enabling you to create a hierarchy of classes to model real-world entities. The Java Persistence API (JPA) offers a powerful and flexible solution for persisting these entities in a relational database. In this article, we'll look at how JPA handles inheritance and explore different strategies for mapping class hierarchies to database tables.
Understanding inheritance in Java
Java supports two types of inheritance: single inheritance, where a class can only extend a single superclass, and multiple inheritance, achieved through interfaces. In the context of JPA, we are primarily interested in single inheritance, which is the ability of a class to inherit attributes and methods from a single parent class.
Consider a scenario where you have a hierarchy of classes representing different types of employee: Employee
, Manager
and Engineer
. The Manager
and Engineer
classes extend the Employee
class, forming an inheritance hierarchy.
public abstract class Employee {
private Long id;
private String name;
private double salary;
}
@Entity
public class Manager extends Employee {
private String department;
}
@Entity
public class Engineer extends Employee {
private String project;
}
1. Single table strategy
In the single table strategy, all the classes in the hierarchy share a single database table, which must contain all the columns required to store the fields of the superclass and all its child classes. A discriminant column is used to distinguish between different types of objects.
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "employee_type")
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
@Entity
@DiscriminatorValue("M")
public class Manager extends Employee {
// ...
}
@Entity
@DiscriminatorValue("E")
public class Engineer extends Employee {
// ...
}
The @DiscriminatorValue
annotation is used to specify the value in the discriminator column which identifies the object type. This value must be unique for all inherited classes. For the example above, the database schema will contain the employee
table, which can be created as follows:
create table employee (
id int(11) AUTO_INCREMENT,
employee_type varchar(1) NOT NULL,
name varchar(255),
salary double,
department varchar(255),
project varchar(255),
primary key (id)
);
2. Join strategy
In the join strategy, each class in the hierarchy is mapped to a separate table. The implementation is very similar to that of the single table strategy, but the database schema is very different.
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "employee_type")
public abstract class Employee {
// ...
}
@Entity
@DiscriminatorValue("M")
public class Manager extends Employee {
// ...
}
@Entity
@DiscriminatorValue("E")
public class Engineer extends Employee {
// ...
}
In this strategy, JPA expects there to be 3 tables in the database schema, which can be created using the following statements:
create table employee (
id int(11) AUTO_INCREMENT,
employee_type varchar(1) NOT NULL,
name varchar(255),
salary double,
primary key (id)
);
create table manager (
id int(11) AUTO_INCREMENT,
department varchar(255),
primary key (id),
foreign key (id) references employee(id)
);
create table engineer (
id int(11) AUTO_INCREMENT,
project varchar(255),
primary key (id),
foreign key (id) references employee(id)
);
3. Table-per-Class strategy
In the Table-per-Class strategy, each class in the hierarchy is mapped to its own table, including both its attributes and those inherited from the superclass.
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Employee {
// ...
}
@Entity
public class Manager extends Employee {
// ...
}
@Entity
public class Engineer extends Employee {
// ...
}
In this configuration, JPA expects there to be 2 tables in the database schema, which can be created using the following statements:
create table manager (
id int(11) AUTO_INCREMENT,
name varchar(255),
salary double,
department varchar(255),
primary key (id),
foreign key (id) references employee(id)
);
create table engineer (
id int(11) AUTO_INCREMENT,
name varchar(255),
salary double,
project varchar(255),
primary key (id),
foreign key (id) references employee(id)
);
alert-info
The employee
table is not required, as the Employee
class is an abstract class and cannot be instantiated.
Conclusion
In this article, we've explored how JPA handles inheritance and discussed three common strategies for mapping class hierarchies to database tables. The choice of strategy depends on the specific requirements of your application and the characteristics of your data model. Understanding these strategies and their implications is crucial to designing efficient and maintainable JPA entities.