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

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.
  • @DiscriminatorColumn can be used to change the discriminator column name and type.
    • name is the column name.
    • discriminatorType is the column type (DiscriminatorType.STRING, DiscriminatorType.CHAR, or DiscriminatorType.INTEGER).
  • @DiscriminatorValue can 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:

DTYPEIDNAMENOFDOORSTONNAGE
Car1Audi4NULL
Car2Smart2NULL
Ship3QueenNULL10000

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:

IDNAMENOFDOORS
1Audi4
2Smart2
IDNAMETONNAGE
3Queen10000

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:

IDNAME
1Audi
2Smart
3Queen
IDNOFDOORS
14
22
IDTONNAGE
310000

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(); }
}