Gymterview
middle

Стратегии наследования в Hibernate

JPA определяет три стратегии маппинга иерархии наследования в реляционные таблицы.

Сравнение стратегий

Критерий SINGLE_TABLE JOINED TABLE_PER_CLASS
Производительность полиморфных запросов Лучшая (одна таблица) Средняя (JOIN) Худшая (UNION)
Нормализация Низкая (NULL-столбцы) Высокая Средняя
NOT NULL ограничения Невозможны для подклассов Возможны Возможны
Добавление подкласса Простое (новый столбец) Новая таблица + FK Новая таблица

SINGLE_TABLE (одна таблица)

Пример
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Payment {
    @Id @GeneratedValue
    private Long id;
    private BigDecimal amount;
}

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
    private String cardNumber;  // null для других типов
}

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

Таблица: payment(id, type, amount, card_number, currency) — все поля в одной таблице, неиспользуемые = NULL.

JOINED (таблица на класс)

Пример
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Payment {
    @Id @GeneratedValue
    private Long id;
    private BigDecimal amount;
}

@Entity
public class CardPayment extends Payment {
    private String cardNumber;
}

Таблицы: payment(id, amount) + card_payment(id, card_number) — данные разнесены, связаны по PK/FK.

TABLE_PER_CLASS (таблица на конкретный класс)

Пример
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Payment {
    @Id @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;
    private BigDecimal amount;
}

Таблицы: card_payment(id, amount, card_number), cash_payment(id, amount, currency) — дублирование общих полей.

Важное

  • SINGLE_TABLE — по умолчанию и самая производительная; идеальна когда подклассов немного
  • JOINED — когда важна нормализация и NOT NULL ограничения; ценой производительности на JOIN
  • TABLE_PER_CLASS — используется редко; полиморфные запросы требуют UNION ALL
  • @DiscriminatorColumn — указывает столбец-дискриминатор для SINGLE_TABLE и JOINED

Частые ошибки

  • TABLE_PER_CLASS с полиморфными запросами — findAll() для базового класса генерирует UNION ALL по всем таблицам
  • SINGLE_TABLE с NOT NULL — поля подклассов не могут быть NOT NULL (они null для других типов)
  • Глубокая иерархия с JOINED — каждый уровень наследования = дополнительный JOIN

Как используется в 2026

  • SINGLE_TABLE — стандартный выбор для большинства случаев
  • Альтернатива наследованию: композиция через @Embedded или паттерн «тип + JSON-данные»
  • В DDD: наследование Entity используется для Value Objects и Aggregate Roots с ограниченной иерархией

На собеседовании: назовите три стратегии и их трейд-оффы. SINGLE_TABLE — быстро, но NULL-столбцы. JOINED — нормализовано, но JOIN-ы. TABLE_PER_CLASS — редко используется из-за UNION ALL. Покажите, что в реальных проектах чаще выбирают SINGLE_TABLE, а глубокое наследование заменяют композицией.