Рубрики
Без рубрики

Карта наследования спячки

Практическое руководство по пониманию различных стратегий отображения наследования с JPA / Hibernate.

Автор оригинала: baeldung.

1. Обзор

Реляционные базы данных не имеют простого способа сопоставить иерархии классов с таблицами баз данных.

Для решения этой проблемы спецификация JPA содержит несколько стратегий:

  • КартаСюперкласс – родительские классы, не могут быть сущностями
  • Присоединился к таблице — каждый класс имеет свою таблицу, и для запроса сущности подкласса требуется присоединение к таблицам
  • Таблица-Per-Класс – все свойства класса, находятся в таблице, поэтому присоединение не требуется

Каждая стратегия приводит к разной структуре базы данных.

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

Поскольку Hibernate является реализацией JPA, она содержит все вышеперечисленное, а также несколько особенностей Hibernate, связанных с наследованием.

В следующих разделах мы более подробно извем на доступные стратегии.

2. КартаСуперкласс

Использование КартаСюперкласс стратегия, наследование проявляется только в классе, но не в модели сущности.

Начнем с создания Лицо класс, который будет представлять родительский класс:

@MappedSuperclass
public class Person {

    @Id
    private long personId;
    private String name;

    // constructor, getters, setters
}

Обратите внимание, что этот класс больше не имеет @Entity аннотация , так как он не будет сохраняться в базе данных сам по себе.

Далее, давайте добавим Сотрудник подкласса:

@Entity
public class MyEmployee extends Person {
    private String company;
    // constructor, getters, setters 
}

В базе данных это будет соответствовать одному “MyEmployee” таблица с тремя столбцами для заявленных и унаследованных полей подкласса.

Если мы используем эту стратегию, предки не могут содержать ассоциации с другими сущностями.

3. Один стол

Стратегия «Единая таблица» создает одну таблицу для каждой иерархии классов. Это также стратегия по умолчанию, выбранная JPA, если мы не указать один явно.

Мы можем определить стратегию, которую мы хотим использовать, добавив @Inheritance аннотация к супер-классу:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class MyProduct {
    @Id
    private long productId;
    private String name;

    // constructor, getters, setters
}

Идентификатор сущностей также определяется в супер-классе.

Затем можно добавить сущности подкласса:

@Entity
public class Book extends MyProduct {
    private String author;
}
@Entity
public class Pen extends MyProduct {
    private String color;
}

3.1. Значения дискриминации

Поскольку записи для всех сущностей будут в одной таблице, Hibernate нужен способ различать их.

По умолчанию это делается через столбец дискриминации, называемый DTYPE который имеет название сущности в качестве значения.

Чтобы настроить столбец дискриминатора, мы можем использовать @DiscriminatorColumn аннотация:

@Entity(name="products")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="product_type", 
  discriminatorType = DiscriminatorType.INTEGER)
public class MyProduct {
    // ...
}

Здесь мы решили дифференцировать MyProduct сущностей подкласса по более интегратор колонка под названием product_type .

Далее, мы должны сказать Hibernate, какое значение каждая подклассная запись будет иметь для product_type столбец:

@Entity
@DiscriminatorValue("1")
public class Book extends MyProduct {
    // ...
}
@Entity
@DiscriminatorValue("2")
public class Pen extends MyProduct {
    // ...
}

Hibernate добавляет два других заранее определенных значения, которые аннотация может принять: « нулевой ” и ” не нулевой “:

  • @DiscriminatorValue (“нулевой”) – означает, что любая строка без значения дискриминатора будет отображена на класс сущности с этой аннотацией; это может быть применено к классу корней иерархии
  • @DiscriminatorValue (“не нулевой”) – любая строка с дискриминационным значением, не соответствующим ни одному из тех, которые связаны с определениями сущности, будет отображена на класс с этой аннотацией

Вместо столбца мы также можем использовать режим Hibernate, @DiscriminatorFormula аннотация для определения дифференцированных значений:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when author is not null then 1 else 2 end")
public class MyProduct { ... }

Эта стратегия имеет преимущество полиморфной производительности запроса, так как только одна таблица должна быть доступирована при запросе родительских сущностей. С другой стороны, это также означает, что мы больше не можем использовать НЕ ограничения на подклассные свойства сущности.

4. Присоединился к таблице

Используя эту стратегию, каждый класс в иерархии отображается в таблице. Единственным столбецом, который неоднократно появляется во всех таблицах, является идентификатор, который будет использоваться для присоединения к ним, когда это необходимо.

Давайте создадим супер-класс, который использует эту стратегию:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {
    @Id
    private long animalId;
    private String species;

    // constructor, getters, setters 
}

Затем мы можем просто определить подкласс:

@Entity
public class Pet extends Animal {
    private String name;

    // constructor, getters, setters
}

Обе таблицы будут иметь animalId столбец идентификатора. Основной ключ Пэт организация также имеет иностранное ключевое ограничение для основного ключа своей материнской сущности. Чтобы настроить эту колонку, мы можем добавить @PrimaryKeyJoinColumn аннотация:

@Entity
@PrimaryKeyJoinColumn(name = "petId")
public class Pet extends Animal {
    // ...
}

Недостатком этого метода отображения наследования является то, что для извлечения сущностей требуется соединения между , что может привести к снижению производительности для большого количества записей.

Количество соединений выше при запросе родительского класса, поскольку он будет присоединяться к каждому связанному ребенку , поэтому производительность, скорее всего, будет затронута чем выше иерархия, которую мы хотим получить записи.

5. Таблица за классом

Стратегия таблицы в классе отображает каждую сущность в таблице, которая содержит все свойства сущности, включая унаследованные.

Полученная схема аналогична схеме с использованием @MappedSuperclass, но в отличие от него, таблица в классе действительно определяет сущности для родительских классов, позволяя в результате ассоциации и полиморфные запросы.

Чтобы использовать эту стратегию, нам нужно только добавить @Inheritance аннотация к базовому классу:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
    @Id
    private long vehicleId;

    private String manufacturer;

    // standard constructor, getters, setters
}

Затем мы можем создать подкатегосы стандартным способом.

Это не сильно отличается от простого отображения каждой сущности без наследования. Различие проявляется при запросе базового класса, который будет возвращать все подклассные записи, а также с помощью UNION заявление в фоновом режиме.

Использование UNION может также привести к низкой производительности при выборе этой стратегии. Другая проблема заключается в том, что мы больше не можем использовать генерацию ключей идентификации.

6. Полиморфные запросы

Как уже упоминалось, запрос базового класса также будет получать все сущности подкласса.

Давайте посмотрим это поведение в действии с тестом JUnit:

@Test
public void givenSubclasses_whenQuerySuperclass_thenOk() {
    Book book = new Book(1, "1984", "George Orwell");
    session.save(book);
    Pen pen = new Pen(2, "my pen", "blue");
    session.save(pen);

    assertThat(session.createQuery("from MyProduct")
      .getResultList()).hasSize(2);
}

В этом примере мы создали два Книжный и Ручка объектов, а затем запросил их супер-класса MyProduct чтобы убедиться, что мы будем получать два объекта.

Hibernate также может запрашивать интерфейсы или базовые классы, которые не являются сущностями, но расширены или реализованы классами сущностей. Давайте посмотрим тест JUnit, используя наши @MappedSuperclass пример:

@Test
public void givenSubclasses_whenQueryMappedSuperclass_thenOk() {
    MyEmployee emp = new MyEmployee(1, "john", "baeldung");
    session.save(emp);

    assertThat(session.createQuery(
      "from com.baeldung.hibernate.pojo.inheritance.Person")
      .getResultList())
      .hasSize(1);
}

Обратите внимание, что это также работает для любого супер-класса или интерфейса, будь то @MappedSuperclass или нет. Отличие от обычного запроса H’L заключается в том, что мы должны использовать полностью квалифицированное имя, так как они не являются объектами, управляемыми Hibernate.

Если мы не хотим, чтобы подкласс возвращался этим типом запроса, то нам нужно только добавить спящий @Polymorphism аннотация к его определению, с типом ЯВНЫЕ :

@Entity
@Polymorphism(type = PolymorphismType.EXPLICIT)
public class Bag implements Item { ...}

В этом случае при запросе Элементы, Сумка записи не будут возвращены.

7. Заключение

В этой статье мы показали различные стратегии отображения наследования в Hibernate.

Полный исходный код примеров можно найти более на GitHub .