Question 30 · Section 16

What Inheritance Types Does JPA Support

JPA supports three entity inheritance strategies, each mapping the class hierarchy to database tables differently. The choice of strategy affects performance, normalization, and...

Language versions: English Russian Ukrainian

Overview

JPA supports three entity inheritance strategies, each mapping the class hierarchy to database tables differently. The choice of strategy affects performance, normalization, and query complexity.


Junior Level

3 Inheritance Strategies

JPA supports 3 inheritance strategies:

  1. SINGLE_TABLE - all classes in one table
  2. JOINED - each class in its own table with JOIN
  3. TABLE_PER_CLASS - each class in its own table (without JOIN)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public class Payment {
    @Id
    private Long id;
    private BigDecimal amount;
}

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
    private String cardNumber;
}

@Entity
@DiscriminatorValue("CASH")
public class CashPayment extends Payment {
    private LocalDate cashDate;
}

Middle Level

SINGLE_TABLE

Table payments:
id | type | amount | card_number | cash_date
1  | CARD | 100    | 1234        | null
2  | CASH | 50     | null        | 2024-01-01

Fast (no JOIN)
Simple queries
Many NULLs for subclasses
Cannot add NOT NULL constraint
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")
public abstract class Payment { }

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment { }

JOINED

Table payments:
id | amount
1  | 100
2  | 50

Table card_payments:
payment_id | card_number
1          | 1234

Table cash_payments:
payment_id | cash_date
2          | 2024-01-01

Normalized
NOT NULL constraints possible
JOIN for every query
Slower on retrieval
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Payment { }

@Entity
@PrimaryKeyJoinColumn(name = "payment_id")
public class CardPayment extends Payment { }

TABLE_PER_CLASS

Table card_payments:
id | amount | card_number
1  | 100    | 1234

Table cash_payments:
id | amount | cash_date
2  | 50     | 2024-01-01

No NULLs, no JOIN for specific type
UNION for parent query
Complex parent queries
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Payment { }

@Entity
public class CardPayment extends Payment { }

TABLE_PER_CLASS is optional in JPA spec. Not all providers support it. Hibernate supports it, but for portability check your provider.

Common Mistakes

// SINGLE_TABLE with many subclasses
// Many fields, many NULLs -> hard to maintain

// JOINED for high-performance
// JOIN for every query -> slow

// TABLE_PER_CLASS with polymorphic queries
// UNION for SELECT FROM Payment -> slow

Senior Level

Strategy Comparison

Strategy INSERT SELECT (type) SELECT (parent) NULLs JOINs Polymorphism
SINGLE_TABLE Fast Fast Fast Many No Yes
JOINED Slow Slow Slow No Yes Yes
TABLE_PER_CLASS Fast Fast (for specific subclass). Slow (UNION ALL on parent query) UNION No No Limited

When to Use What

SINGLE_TABLE:
Small hierarchy (2-3 subclasses)
Subclass fields can be NULL
High performance needed
Polymorphic queries frequent

JOINED:
Normalization matters
Subclass fields NOT NULL
Rare polymorphic queries
Different subclasses have many fields

TABLE_PER_CLASS:
Each subclass is independent entity
Polymorphic queries rare
No common fields among subclasses

Polymorphic Queries

// SINGLE_TABLE - one query
SELECT p FROM Payment p  // SELECT * FROM payments

// JOINED - JOIN all tables
SELECT p FROM Payment p  // SELECT * FROM payments p
                          // LEFT JOIN card_payments cp ON ...
                          // LEFT JOIN cash_payments cash ON ...

// TABLE_PER_CLASS - UNION
SELECT p FROM Payment p  // SELECT * FROM card_payments
                          // UNION ALL
                          // SELECT * FROM cash_payments

TYPE and TREAT in JPQL

// TYPE - filter by type
@Query("SELECT p FROM Payment p WHERE TYPE(p) = CardPayment")
List<CardPayment> findCardPayments();

// TREAT - cast to subclass
@Query("SELECT p FROM Payment p JOIN TREAT(p AS CardPayment) cp WHERE cp.cardNumber = :number")
List<Payment> findByCardNumber(@Param("number") String number);

Hibernate 6 Improvements

Hibernate 6:
- Improved TABLE_PER_CLASS handling
- Better JOIN optimization for JOINED
- Automatic discriminator for SINGLE_TABLE

Advanced Patterns

// Pattern: @MappedSuperclass for common fields
@MappedSuperclass
public abstract class BaseEntity {
    @Id
    private Long id;
    private LocalDateTime createdAt;
}

@Entity
public class CardPayment extends BaseEntity { }

@Entity
public class CashPayment extends BaseEntity { }
// Each entity - separate table
// No polymorphic queries

@MappedSuperclass - NOT an entity: cannot query it, no table, cannot be used in polymorphic queries. It’s just a template for common fields. Classic interview trap: “How does @MappedSuperclass differ from @Entity?”

Best Practices

SINGLE_TABLE for simple cases (2-3 subclasses)
JOINED for normalization and NOT NULL
TABLE_PER_CLASS for independent subclasses
Avoid deep inheritance hierarchies (>3 levels). Reason: deep hierarchies complicate queries (more JOINs in JOINED, more columns in SINGLE_TABLE, more UNION branches in TABLE_PER_CLASS), make domain model harder to understand, and create cascading problems on changes.
@MappedSuperclass when polymorphism not needed
TYPE/TREAT for typed queries

SINGLE_TABLE with many subclasses
JOINED for high-performance systems
TABLE_PER_CLASS with frequent polymorphic queries
Deep inheritance hierarchies

Interview Cheat Sheet

Must know:

  • 3 strategies: SINGLE_TABLE (one table), JOINED (table per class), TABLE_PER_CLASS (table per class, UNION for parent)
  • SINGLE_TABLE - fast (no JOIN), but many NULLs, cannot use NOT NULL constraint
  • JOINED - normalized, NOT NULL possible, but JOIN for every query - slow
  • TABLE_PER_CLASS - no NULLs/JOINs for specific type, but UNION for polymorphic queries
  • @MappedSuperclass - NOT an entity, no table, no polymorphic queries - template for common fields
  • TYPE for filtering by type, TREAT for casting to subclass in JPQL

Frequent follow-up questions:

  • Which strategy to choose? SINGLE_TABLE for 2-3 subclasses and speed; JOINED for normalization; TABLE_PER_CLASS for independent subclasses
  • Why is deep hierarchy bad? More JOINs (JOINED), more columns (SINGLE_TABLE), more UNIONs (TABLE_PER_CLASS)
  • @MappedSuperclass vs @Entity with inheritance? @MappedSuperclass - no polymorphic queries, no table; @Entity - has both
  • Is TABLE_PER_CLASS optional in JPA spec? Yes - not all providers support it, Hibernate does

Red flags (DO NOT say):

  • “SINGLE_TABLE with 10 subclasses” - many NULLs, hard to maintain
  • “JOINED for high-performance system” - JOIN for every query, slow
  • “TABLE_PER_CLASS with frequent polymorphic queries” - UNION ALL, even slower
  • “@MappedSuperclass for polymorphic queries” - cannot query @MappedSuperclass

Related topics:

  • [[26. What is JPQL and How Does It Differ from SQL]]
  • [[27. What is Criteria API and When to Use It]]
  • [[23. How to Properly Use @OneToMany and @ManyToOne]]
  • [[24. What Are the Peculiarities of Bidirectional Relationships]]