04 JPA Inheritance
Because databases are not object-oriented, mapping object inheritance to database tables can be challenging. JPA provides multiple strategies to map inheritance hierarchies to database tables.
The example hierarchy consists of a Vehicle class with subclasses Car and Ship.
classDiagram
class Vehicle {
<<abstract>>
+ id: Integer
+ name: String
}
class Car {
+ nOfDoors: int
}
class Ship {
+ tonnage: double
}
Vehicle <|-- Car
Vehicle <|-- Ship
@Entity annotation!Single Table
The default strategy is to store all classes in a single table (@Inheritance(strategy = InheritanceType.SINGLE_TABLE)).
The following has to be considered:
- All attributes of all classes are stored in the same table.
- A discriminator column is added to differentiate between classes.
@DiscriminatorColumncan be used to change the discriminator column name and type.nameis the column name.discriminatorTypeis the column type (DiscriminatorType.STRING,DiscriminatorType.CHAR, orDiscriminatorType.INTEGER).
@DiscriminatorValuecan be used to change the discriminator value for a class.- All fields of subclasses must be nullable.
- Foreign keys can only reference the superclass table (cars and ships are no known entities in the database).
In our case the database table would look like this:
| DTYPE | ID | NAME | NOFDOORS | TONNAGE |
|---|---|---|---|---|
| Car | 1 | Audi | 4 | NULL |
| Car | 2 | Smart | 2 | NULL |
| Ship | 3 | Queen | NULL | 10000 |
Table per Class
Each concrete class is stored in its own table (@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)).
This strategy is not supported by all JPA providers.
The following has to be considered:
- Each class has its own table.
- The superclass table is not created.
- Queries are simpler because no joins are required.
- The database schema is normalized, fields can be defined as
NOT NULL. - Foreign keys can only reference the subclass tables.
- Polymorphic queries are not supported.
- Identity generation is not supported, because key uniqueness must be guaranteed across multiple tables.
In our case the database tables would look like this:
| ID | NAME | NOFDOORS |
|---|---|---|
| 1 | Audi | 4 |
| 2 | Smart | 2 |
| ID | NAME | TONNAGE |
|---|---|---|
| 3 | Queen | 10000 |
Joined
Each class is stored in its own table, and a join is used to retrieve the data (@Inheritance(strategy = InheritanceType.JOINED)).
The following has to be considered:
- Each class has its own table.
- The primary key of the subclass table is also a foreign key to the superclass table.
- Queries are more complex because joins are required.
- The database schema is normalized, fields can be defined as
NOT NULL. - Foreign keys can reference the subclass tables.
- Polymorphic queries can be used to retrieve all subclasses.
In our case the database tables would look like this:
| ID | NAME |
|---|---|
| 1 | Audi |
| 2 | Smart |
| 3 | Queen |
| ID | NOFDOORS |
|---|---|
| 1 | 4 |
| 2 | 2 |
| ID | TONNAGE |
|---|---|
| 3 | 10000 |
Mapped Superclass
The superclass is not an entity, but only provides common attributes to subclasses (@MappedSuperclass).
This way, there will be no table generated for the annotated class, which can be used to avoid code duplication.
The class annotated with @MappedSuperclass must not be annotated with @Entity.
Subclasses are still required to be annotated with @Entity.
@MappedSuperclass
public abstract class UuidEntity {
@Id
protected String id;
public UuidEntity() { this.id = UUID.randomUUID().toString(); }
public String getId() { return id; }
public boolean equals(Object obj) {
return x instanceof UuidEntity && ((UuidEntity) x).id.equals(id);
}
public int hashCode() { return id.hashCode(); }
}