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, а глубокое наследование заменяют композицией.