title | shortTitle | description | category | language | tag | |||||
---|---|---|---|---|---|---|---|---|---|---|
Table Inheritance Pattern in Java: Modeling Hierarchical Data in Relational Databases |
Table Inheritance |
Explore the Table Inheritance pattern in Java with real-world examples, database schema, and tutorials. Learn how to model class hierarchies elegantly in relational databases. |
Data access |
en |
|
- Class Table Inheritance
- Joined Table Inheritance
Represent inheritance hierarchies in relational databases by mapping each class in a hierarchy to a database table.
Real-world example
A classic real-world analogy for the Table Inheritance (Joined Table) pattern is managing employee records in an organization: Imagine a company's database storing information about employees. All employees have common attributes (name, employee ID, hire date), stored in a general "Employee" table. However, the company also has different types of employees: Full-time Employees (with a salary and benefits) and Contractors (hourly rate, contract duration). Each employee type has distinct data stored in separate specialized tables ("FullTimeEmployee" and "Contractor"), which reference the main "Employee" table. This structure mirrors the Table Inheritance pattern—shared fields in a common table and unique fields split into subclass-specific tables.
In plain words
The Table Inheritance pattern maps each class within an inheritance hierarchy to its own database table, storing common attributes in a base table and subclass-specific attributes in separate joined tables.
Martin Fowler says
Relational databases don't support inheritance, which creates a mismatch when mapping objects. To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining relationships through foreign keys, making it easier to link the classes together in the database.
Mind map
The Vehicle
class will be the superclass, and we will have subclasses Car
and Truck
that extend Vehicle
. The superclass Vehicle
stores common attributes, while subclasses store their own specific attributes.
Superclass (Vehicle
):
The superclass stores shared attributes:
make
: Manufacturer of the vehicle.model
: Model of the vehicle.year
: Year of manufacture.id
: Unique identifier for the vehicle.
These common attributes will reside in a dedicated database table (Vehicle
table).
Subclasses (Car
and Truck
):
Each subclass adds attributes specific to its type:
Car
:numberOfDoors
, indicating how many doors the car has.Truck
:payloadCapacity
, representing how much payload the truck can carry.
Each subclass stores these specific attributes in their respective tables (Car
and Truck
tables).
Foreign Key Relationship:
Each subclass table references the superclass table via a foreign key. The subclass's id
links to the primary key of the superclass, thus connecting common and subclass-specific data.
@Setter
@Getter
public class Vehicle {
private String make;
private String model;
private int year;
private int id;
public Vehicle(int year, String make, String model, int id) {
this.make = make;
this.model = model;
this.year = year;
this.id = id;
}
@Override
public String toString() {
return "Vehicle{"
+ "id="
+ id
+ ", make='"
+ make
+ '\''
+ ", model='"
+ model
+ '\''
+ ", year="
+ year
+ '}';
}
}
@Getter
public class Car extends Vehicle {
private int numDoors;
public Car(int year, String make, String model, int numDoors, int id) {
super(year, make, model, id);
if (numDoors <= 0) {
throw new IllegalArgumentException("Number of doors must be positive.");
}
this.numDoors = numDoors;
}
public void setNumDoors(int doors) {
if (doors <= 0) {
throw new IllegalArgumentException("Number of doors must be positive.");
}
this.numDoors = doors;
}
@Override
public String toString() {
return "Car{"
+ "id="
+ getId()
+ ", make='"
+ getMake()
+ '\''
+ ", model='"
+ getModel()
+ '\''
+ ", year="
+ getYear()
+ ", numberOfDoors="
+ getNumDoors()
+ '}';
}
}
@Getter
public class Truck extends Vehicle {
private double loadCapacity;
public Truck(int year, String make, String model, double loadCapacity, int id) {
super(year, make, model, id);
if (loadCapacity <= 0) {
throw new IllegalArgumentException("Load capacity must be positive.");
}
this.loadCapacity = loadCapacity;
}
public void setLoadCapacity(double capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Load capacity must be positive.");
}
this.loadCapacity = capacity;
}
@Override
public String toString() {
return "Truck{"
+ "id="
+ getId()
+ ", make='"
+ getMake()
+ '\''
+ ", model='"
+ getModel()
+ '\''
+ ", year="
+ getYear()
+ ", payloadCapacity="
+ getLoadCapacity()
+ '}';
}
}
@Entity
: Indicates that the class is a JPA entity mapped to a database table.@Inheritance(strategy = InheritanceType.JOINED)
: Configures joined table inheritance, meaning each class (superclass and subclasses) maps to its own table.@Table(name = "XYZ")
: Explicitly specifies the database table name for clarity.@Id
: Marks the primary key of the entity.@GeneratedValue(strategy = GenerationType.IDENTITY)
: Specifies auto-generation of primary key values by the database.
Applying this code will result in three database tables structured as follows:
Vehicle table
- id
- make
- model
- year
Car table
- id (FK to Vehicle)
- numberOfDoors
Truck table
- id (FK to Vehicle)
- payloadCapacity
This approach clearly represents the Table Inheritance (Joined Table) pattern, with common attributes centrally managed in the superclass table and subclass-specific attributes cleanly separated in their own tables.
- When persisting an inheritance hierarchy of Java classes in a relational database.
- Suitable when classes share common attributes but also have distinct fields.
- Beneficial when polymorphic queries across subclasses are frequent.
- Hibernate ORM (
@Inheritance(strategy = InheritanceType.JOINED)
in Java) - EclipseLink (Joined Inheritance strategy in JPA)
- Spring Data JPA applications modeling complex domain hierarchies.
Benefits:
- Normalized database schema reduces redundancy.
- Clearly models class hierarchies at the database level.
- Easier to implement polymorphic queries due to clear class distinctions.
Trade-offs:
- Increased complexity in database queries involving multiple joins.
- Reduced performance for deep inheritance hierarchies due to costly joins.
- Maintenance overhead increases with the complexity of inheritance structures.
- Single Table Inheritance: Alternative strategy mapping an entire class hierarchy into a single database table, useful when fewer joins are preferred at the cost of nullable columns.
- Concrete Table Inheritance – Each subclass has its own standalone table; related in providing an alternate approach to storing inheritance hierarchies.