← Назад к вопросам

Как представить наследование в базе данных

1.7 Middle🔥 121 комментариев
#ORM и Hibernate#Базы данных и SQL#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Представление наследования в базе данных

Это классическая архитектурная задача на пересечении ООП (Java) и реляционных БД. Существует три основных подхода, каждый с плюсами и минусами.

1. Single Table Inheritance (STI) — одна таблица для всех

Идея: вся иерархия классов хранится в одной таблице с колонкой-дискриминатором.

CREATE TABLE employees (
  id BIGINT PRIMARY KEY,
  type VARCHAR(50) NOT NULL,  -- дискриминатор
  name VARCHAR(255),
  salary DECIMAL(10, 2),
  department_id BIGINT,       -- для Manager
  years_experience INT        -- для Developer
);

Java код:

@Entity
@Table(name = "employees")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Employee {
  @Id
  private Long id;
  private String name;
  private BigDecimal salary;
}

@Entity
@DiscriminatorValue("MANAGER")
public class Manager extends Employee {
  @ManyToOne
  @JoinColumn(name = "department_id")
  private Department department;
}

@Entity
@DiscriminatorValue("DEVELOPER")
public class Developer extends Employee {
  private Integer yearsExperience;
}

Плюсы:

  • Быстрые запросы (нет JOIN)
  • Простая логика
  • Легко добавить новый тип

Минусы:

  • Много NULL колонок (если иерархия разветвлённая)
  • Слабая типизация на уровне БД
  • Можно потеряться в смысле таблицы

2. Joined Table Inheritance (JTI) — таблица на класс

Идея: каждый класс имеет свою таблицу, связанные по первичному ключу.

CREATE TABLE employees (
  id BIGINT PRIMARY KEY,
  name VARCHAR(255),
  salary DECIMAL(10, 2)
);

CREATE TABLE managers (
  id BIGINT PRIMARY KEY,
  department_id BIGINT,
  FOREIGN KEY (id) REFERENCES employees(id) ON DELETE CASCADE
);

CREATE TABLE developers (
  id BIGINT PRIMARY KEY,
  years_experience INT,
  FOREIGN KEY (id) REFERENCES employees(id) ON DELETE CASCADE
);

Java код:

@Entity
@Table(name = "employees")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Employee {
  @Id
  private Long id;
  private String name;
  private BigDecimal salary;
}

@Entity
@Table(name = "managers")
public class Manager extends Employee {
  @ManyToOne
  @JoinColumn(name = "department_id")
  private Department department;
}

@Entity
@Table(name = "developers")
public class Developer extends Employee {
  private Integer yearsExperience;
}

Плюсы:

  • Нормализованная схема
  • Чистая типизация
  • Хорошо масштабируется

Минусы:

  • Много JOIN для получения полного объекта
  • Медленнее чем STI на больших объёмах
  • Сложнее с полиморфными запросами

3. Table Per Class (TPC) — отдельная таблица с дублированием

Идея: каждый конкретный класс имеет полную таблицу (без базовой таблицы).

CREATE TABLE managers (
  id BIGINT PRIMARY KEY,
  name VARCHAR(255),
  salary DECIMAL(10, 2),
  department_id BIGINT
);

CREATE TABLE developers (
  id BIGINT PRIMARY KEY,
  name VARCHAR(255),
  salary DECIMAL(10, 2),
  years_experience INT
);

Java код:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Employee {
  @Id
  private Long id;
  private String name;
  private BigDecimal salary;
}

@Entity
@Table(name = "managers")
public class Manager extends Employee {
  @ManyToOne
  @JoinColumn(name = "department_id")
  private Department department;
}

@Entity
@Table(name = "developers")
public class Developer extends Employee {
  private Integer yearsExperience;
}

Плюсы:

  • Каждая таблица независима
  • Полиморфные запросы не требуют UNION

Минусы:

  • Дублирование данных и логики
  • Сложно обновлять базовые свойства
  • Нарушает нормализацию
  • Генерирует сложный SQL (UNION по всем таблицам)

Рекомендации для выбора

СценарийСтратегияПричина
Простая иерархия (2-3 типа)STIБыстро, просто, нет NULL
Сложная иерархия, разные поляJTIНормализация, чистота схемы
Полиморфные запросы частоSTIОдин SELECT вместо UNION
Высокие требования к производительностиSTIМинимум JOIN
Важна типизация БДJTIОтдельные таблицы явно показывают типы

Я лично предпочитаю JTI в большинстве случаев: нормализованная схема, хорошо масштабируется, понятна новым разработчикам.


Гибридный подход

Иногда комбинируют стратегии:

// Сильно разветвлённая иерархия
// Верхний уровень: JTI
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Employee {...}

// Нижний уровень: STI в отдельных таблицах
@Entity
@Table(name = "developers")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dev_type")
public abstract class Developer extends Employee {...}

@Entity
@DiscriminatorValue("BACKEND")
public class BackendDeveloper extends Developer {...}

Это достаточно редко используется, но мощно для сложных доменов.

Как представить наследование в базе данных | PrepBro