Питання 30 · Розділ 16

Які типи наслідування підтримує JPA

JPA підтримує три стратегії наслідування сутностей, кожна з яких по-різному мапить ієрархію класів на таблиці бази даних. Вибір стратегії впливає на продуктивність, нормалізацію...

Мовні версії: English Russian Ukrainian

Огляд

JPA підтримує три стратегії наслідування сутностей, кожна з яких по-різному мапить ієрархію класів на таблиці бази даних. Вибір стратегії впливає на продуктивність, нормалізацію та складність запитів.


🟢 Junior Level

3 стратегії наслідування

JPA підтримує 3 стратегії наслідування:

  1. SINGLE_TABLE — всі класи в одній таблиці
  2. JOINED — кожен клас в своїй таблиці з JOIN
  3. TABLE_PER_CLASS — кожен клас в своїй таблиці (без 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

Таблиця payments:
id | type | amount | card_number | cash_date
1  | CARD | 100    | 1234        | null
2  | CASH | 50     | null        | 2024-01-01

✅ Швидко (немає JOIN)
✅ Прості запити
❌ Багато NULL для підкласів
❌ Не можна додати NOT NULL constraint

JOINED

Таблиця payments:
id | amount
1  | 100
2  | 50

Таблиця card_payments:
payment_id | card_number
1          | 1234

✅ Нормалізовано
✅ NOT NULL constraints можливі
❌ JOIN для кожного запиту
❌ Повільніше при вибірці

TABLE_PER_CLASS

Таблиця card_payments:
id | amount | card_number
1  | 100    | 1234

Таблиця cash_payments:
id | amount | cash_date
2  | 50     | 2024-01-01

✅ Немає NULL, немає JOIN для конкретного типу
❌ UNION для запиту батька
❌ Складні запити до батька

TABLE_PER_CLASS — optional в JPA spec. Не всі провайдери підтримують. Hibernate підтримує, але для portability перевіряйте ваш провайдер.


🔴 Senior Level

Порівняння стратегій

Стратегія INSERT SELECT (тип) SELECT (батько) NULL JOIN Поліморфізм
SINGLE_TABLE Швидко Швидко Швидко Багато Ні
JOINED Повільно Повільно Повільно Ні Так
TABLE_PER_CLASS Швидко Швидко/Повільно UNION Ні Ні ⚠️

Коли що використовувати

SINGLE_TABLE:
✅ Маленька ієрархія (2-3 підкласи)
✅ Поля підкласів можуть бути NULL
✅ Висока продуктивність потрібна

JOINED:
✅ Нормалізація важлива
✅ Поля підкласів NOT NULL
✅ Рідкісні поліморфні запити

TABLE_PER_CLASS:
✅ Кожен підклас — незалежна сутність
✅ Поліморфні запити рідкісні

TYPE та TREAT в JPQL

// TYPE — фільтр по типу
@Query("SELECT p FROM Payment p WHERE TYPE(p) = CardPayment")
List<CardPayment> findCardPayments();

// TREAT — cast до підкласу
@Query("SELECT p FROM Payment p JOIN TREAT(p AS CardPayment) cp WHERE cp.cardNumber = :number")
List<Payment> findByCardNumber(@Param("number") String number);

@MappedSuperclass

@MappedSuperclass
public abstract class BaseEntity {
    @Id
    private Long id;
    private LocalDateTime createdAt;
}

@Entity
public class CardPayment extends BaseEntity { }

@MappedSuperclass — НЕ entity: не можна робити запити до нього, немає таблиці, не можна використовувати в поліморфних запитах. Це просто шаблон для спільних полів.

Best Practices

✅ SINGLE_TABLE для простих випадків (2-3 підкласи)
✅ JOINED для нормалізації та NOT NULL
✅ TABLE_PER_CLASS для незалежних підкласів
✅ Уникати глибокої ієрархії (>3 рівнів)
✅ @MappedSuperclass коли не потрібен поліморфізм

❌ SINGLE_TABLE з багатьма підкласами
❌ JOINED для high-performance систем
❌ TABLE_PER_CLASS з частими поліморфними запитами

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • 3 стратегії: SINGLE_TABLE (одна таблиця), JOINED (таблиця на клас), TABLE_PER_CLASS (таблиця на клас, UNION для батька)
  • SINGLE_TABLE — швидко (немає JOIN), але багато NULL, не можна NOT NULL constraint
  • JOINED — нормалізовано, NOT NULL можливі, але JOIN для кожного запиту — повільно
  • TABLE_PER_CLASS — немає NULL/JOIN для конкретного типу, але UNION для поліморфних запитів
  • @MappedSuperclass — НЕ entity, немає таблиці, не можна поліморфні запити — шаблон для спільних полів
  • TYPE для фільтру по типу, TREAT для cast до підкласу в JPQL

Пов’язані теми:

  • [[26. Що таке JPQL і чим він відрізняється від SQL]]
  • [[27. Що таке Criteria API і коли його використовувати]]
  • [[23. Як правильно використовувати @OneToMany і @ManyToOne]]
  • [[24. У чому особливості bidirectional relationships]]