Mapping an inheritance hierarchy

Hibernate · lesson 19 of 32 · 4 min read

Pick between one shared table, a table per class and a joined hierarchy with eyes open.

Open this lesson in the learning hub

Key points

  • SINGLE_TABLE is the default: every subclass shares one table plus a discriminator column. No joins, fastest to read.
  • The price is that every column a subclass adds must be nullable, so the database can no longer enforce a required field.
  • JOINED gives each class its own table and keeps the constraints, at the cost of a join per level on every read.
  • TABLE_PER_CLASS copies the parent columns into each subclass table. A query on the parent becomes a UNION ALL.
  • @MappedSuperclass is not inheritance mapping - it only shares fields, and you cannot query or reference it polymorphically.
  • If the subclasses share almost nothing, use separate entities. Inheritance in your domain model does not have to reach the schema.

Example

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")     // payment_type = CARD | BANK
public abstract class Payment {
    @Id @GeneratedValue private Long id;
    private long amountCents;
}

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
    private String last4;          // must be nullable: BANK rows have no last4
}

@Entity
@DiscriminatorValue("BANK")
public class BankPayment extends Payment {
    private String iban;           // nullable for the same reason
}

// select p from Payment p
//   SINGLE_TABLE     -> select * from payment
//   JOINED           -> payment left join card_payment left join bank_payment
//   TABLE_PER_CLASS  -> select ... from card_payment union all select ... from bank_payment

SINGLE_TABLE unless you need the not-null constraints back.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.