Lesson 9 / 25
Inheritance Mapping Strategies
Map class hierarchies with SINGLE_TABLE, JOINED, TABLE_PER_CLASS and @MappedSuperclass.
Tables for a class hierarchy
Relational databases have no inheritance, so JPA offers strategies. @MappedSuperclass is not an entity: it just shares mapped fields (such as id, createdAt, version) with subclasses, each of which has its own independent table; you cannot query the superclass polymorphically. SINGLE_TABLE (the default for @Inheritance) stores all subclasses in one table with a discriminator column (@DiscriminatorColumn); it is the fastest for polymorphic queries (no joins), but subclass-specific columns must be nullable, which weakens constraints. JOINED gives each class its own table, with subclass tables linked by primary key to the parent table; it keeps a normalised schema with proper NOT NULL constraints, at the cost of joins for every polymorphic query. TABLE_PER_CLASS gives each concrete class a complete table; polymorphic queries need UNIONs and sequence-based IDs, so it is rarely the best choice. Choose by query patterns: many polymorphic queries favour SINGLE_TABLE; strict constraints and subclass-heavy queries favour JOINED. Often, composition or a simple type field is better than mapping deep hierarchies.
SINGLE_TABLE and JOINED side by side
Payments in one table with a discriminator; accounts split across joined tables.
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE) protected Long id;
@Column(nullable = false, updatable = false) protected Instant createdAt = Instant.now();
}
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "method", length = 10)
public abstract class Payment extends BaseEntity {
protected BigDecimal amount;
}
@Entity @DiscriminatorValue("UPI") public class UpiPayment extends Payment { private String vpa; }
@Entity @DiscriminatorValue("CARD") public class CardPayment extends Payment { private String last4; }
// one table: payments(id, created_at, method, amount, vpa NULL, last4 NULL)
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Account extends BaseEntity { protected String owner; }
@Entity public class SavingsAccount extends Account { @Column(nullable = false) private BigDecimal rate; }
@Entity public class LoanAccount extends Account { @Column(nullable = false) private BigDecimal principal; }
// tables: account(id, created_at, owner), savings_account(id FK, rate), loan_account(id FK, principal)Polymorphic queries decide the strategy
If you mostly query select p from Payment p, SINGLE_TABLE avoids joins. If you mostly query one subtype and need NOT NULL constraints on its columns, JOINED is cleaner. Measure the queries you actually run.
Quick check: Which inheritance strategy stores all subclasses in one table with a discriminator column?
- JOINED
- TABLE_PER_CLASS
- SINGLE_TABLE
- @MappedSuperclass
Answer
SINGLE_TABLE — SINGLE_TABLE uses one table plus a discriminator to tell rows apart.