Explain the different inheritance mapping strategies in JPA.
JPA (Java Persistence API) allows mapping Java inheritance hierarchies to database tables. This is crucial for object-oriented design patterns where sub-classes extend super-classes, enabling polymorphic queries and operations. JPA provides three primary strategies to manage how these hierarchies are persisted in the relational database.
1. Single Table Strategy
In the Single Table strategy, the entire inheritance hierarchy (superclass and all subclasses) is mapped to a single database table. A discriminator column is added to this table to distinguish between instances of different concrete classes.
- Annotation:
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) - Discriminator column:
@DiscriminatorColumn(name = "DTYPE", discriminatorType = DiscriminatorType.STRING)
Advantages
- Simplicity: Easy to implement.
- Performance: Excellent for polymorphic queries (e.g., fetching all
Paymentobjects, regardless of type) as it only involves one table. No joins required.
Disadvantages
- Denormalization: The table will contain many nullable columns, as each subclass might have unique fields not relevant to other subclasses or the superclass. This can lead to wasted space and integrity issues.
- Column constraints: Cannot enforce
NOT NULLconstraints on subclass-specific columns directly at the database level for fields that don't apply to all types.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "VEHICLE_TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class Vehicle {
@Id
private Long id;
private String manufacturer;
// ... getters and setters
}
@Entity
@DiscriminatorValue("CAR")
public class Car extends Vehicle {
private int numberOfDoors;
// ...
}
@Entity
@DiscriminatorValue("BIKE")
public class Bike extends Vehicle {
private boolean hasSidecar;
// ...
}
2. Joined Table Strategy
The Joined Table strategy maps each class in the hierarchy to its own database table. The superclass table stores common attributes, while subclass tables store their specific attributes and also contain a foreign key column referencing the primary key of the superclass table. This forms a one-to-one relationship between a superclass row and its corresponding subclass row.
- Annotation:
@Inheritance(strategy = InheritanceType.JOINED) - Subclass foreign key:
@PrimaryKeyJoinColumn(name="VEHICLE_ID")
Advantages
- Normalization: Data is well-normalized with no nullable columns for subclass-specific fields.
- Data Integrity: Allows enforcing
NOT NULLconstraints and other database-level constraints on all columns. - Flexibility: Better for large hierarchies with many subclass-specific fields.
Disadvantages
- Performance: Polymorphic queries (e.g.,
SELECT * FROM Vehicle) require joins across multiple tables, which can impact performance, especially for deep hierarchies. - Complexity: More complex schema due to multiple tables and join conditions.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Vehicle {
@Id
private Long id;
private String manufacturer;
// ...
}
@Entity
@PrimaryKeyJoinColumn(name = "VEHICLE_ID") // optional, defaults to parent PK name
public class Car extends Vehicle {
private int numberOfDoors;
// ...
}
@Entity
@PrimaryKeyJoinColumn(name = "VEHICLE_ID")
public class Bike extends Vehicle {
private boolean hasSidecar;
// ...
}
3. Table Per Class Strategy
In the Table Per Class strategy, each concrete class in the hierarchy (including abstract superclasses if they are directly queried) is mapped to its own table. There is no table for the abstract superclass. Each table contains columns for all inherited attributes and its own specific attributes. This strategy is generally discouraged by JPA providers due to its limitations.
- Annotation:
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) - Superclass often has
@Entitybut no@Tableif it's abstract and not directly queryable. If concrete, it gets its own table. If it's abstract and mapped, it typically has a table.
Advantages
- Normalization: Each table is self-contained and fully normalized.
- Simplicity: Each concrete class maps to a single table, making it easy to understand for that specific class.
Disadvantages
- No Polymorphic Identity: Hibernate, a popular JPA implementation, does not support polymorphic associations with this strategy by default (e.g., a
Garageentity having aList<Vehicle>will fail without extra configuration). - Performance: Polymorphic queries require
UNION ALLoperations across all subclass tables, which can be very inefficient, especially with many subclasses. - Primary Key Duplication: If
IDENTITYstrategy is used for primary key generation, IDs can clash across tables; typically requiresSEQUENCEorTABLEstrategy. - Abstract Superclass: If the superclass is abstract, no table is created for it. If you query for the abstract superclass, JPA needs to
UNIONall subclass tables.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE) // IDENTITY won't work across tables
private Long id;
private String manufacturer;
// ...
}
@Entity
public class Car extends Vehicle {
private int numberOfDoors;
// ...
}
@Entity
public class Bike extends Vehicle {
private boolean hasSidecar;
// ...
}
Comparison of Inheritance Strategies
| Feature | Single Table | Joined Table | Table Per Class |
|---|---|---|---|
| Database Schema | 1 table for entire hierarchy | 1 table per class (superclass + subclasses) | 1 table per concrete class |
| Normalization | Poor (many nullable columns) | Excellent | Excellent |
| Polymorphic Queries | Fast (no joins) | Requires joins (can be slow) | Requires UNION ALL (often slowest) |
| Data Integrity | Challenging for subclass-specific fields | Good (via FKs and constraints) | Good (per table) |
| Complexity | Low | Medium | Medium-High (due to PK generation, polymorphic query issues) |
| Recommended Usage | Small, simple hierarchies; few subclass-specific fields. | Complex hierarchies; good normalization needed; frequent queries for specific subclasses. | Generally discouraged due to performance and ORM limitations. Consider only for legacy schemas that strictly require it. |