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

Руководство по JPA с отображением наследования в режиме гибернации

Автор оригинала: François Dupire.

Вступление

В этой статье мы погрузимся в Сопоставление наследования с JPA и спящий режим в Java .

API сохраняемости Java (JPA) является стандартом сохраняемости экосистемы Java. Это позволяет нам сопоставлять нашу модель предметной области непосредственно со структурой базы данных, а затем дает нам возможность гибко манипулировать только объектами в нашем коде. Это позволяет нам не возиться с громоздкими компонентами JDBC, такими как Соединение , Набор результатов и т.д.

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

  • Руководство по JPA с гибернацией: Базовое сопоставление
  • Руководство по JPA с гибернацией: Сопоставление отношений
  • Руководство по JPA с гибернацией: Сопоставление наследования ( ты здесь! )
  • Руководство по JPA с гибернацией: Запросы ( скоро! )

Сопоставление наследования

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

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

  • Сопоставленный Суперкласс
  • Один Стол
  • Один Стол На (Конкретный) Класс
  • Объединенный Стол

Модель предметной области

Прежде всего, давайте добавим некоторое наследование в нашу модель домена:

Как мы видим, мы представили класс Person , который является суперклассом как Учителя , так и Ученика и содержит имена и дату рождения, а также адрес и пол.

В дополнение к этому мы добавили иерархию Транспортное средство для управления транспортными средствами учителя для управления парковкой.

Это может быть Автомобиль или Мотоцикл . У каждого транспортного средства есть номерной знак, но автомобиль может работать на сжиженном газе (что запрещено на определенных уровнях парковки), а у мотоциклов может быть коляска (для которой требуется парковочное место автомобиля).

Отображенный Супер-Класс

Давайте начнем с простого-подхода сопоставленного суперкласса. Сопоставленный суперкласс-это класс, который не является сущностью, а содержит сопоставления. Это тот же принцип, что и встроенные классы, но применяется к наследованию.

Итак, предположим, что мы хотим сопоставить наши новые классы для обработки парковки учителей в школе, мы сначала определим Транспортное средство класс с аннотацией @MappedSuperclass :

@MappedSuperclass
public class Vehicle {
    @Id
    private String licensePlate;
}

Он содержит только идентификатор, помеченный @Id , который является номерным знаком транспортного средства.

Теперь мы хотим сопоставить наши две сущности: Автомобиль и Мотоцикл . Оба будут распространяться на Транспортное средство и унаследуют номерной знак :

@Entity
class Car extends Vehicle {
    private boolean runOnLpg;
}

@Entity
class Motorcycle extends Vehicle {
    private boolean hasSideCar;
}

Хорошо, теперь мы определили сущности, и они наследуются от Транспортного средства . Хотя, что происходит на стороне базы данных? JPA генерирует эти определения таблиц:

create table Car (licensePlate varchar(255) not null, runOnLpg boolean not null, primary key (licensePlate))
create table Motorcycle (licensePlate varchar(255) not null, hasSideCar boolean not null, primary key (licensePlate))

Каждая сущность имеет свою собственную таблицу, обе с номерным знаком столбцом, который также является первичным ключом этих таблиц. Нет Транспортного средства стола . @MappedSuperclass не является сущностью. Фактически, к классу не могут быть применены аннотации @Entity и @MappedSuperclass .

Каковы последствия того, что Транспортное средство не является юридическим лицом? Ну, мы не можем искать Транспортное средство с помощью EntityManager .

Давайте добавим несколько автомобилей и мотоцикл:

insert into CAR(LICENSEPLATE, RUNONLPG) values('1 - ABC - 123', '1');
insert into CAR(LICENSEPLATE, RUNONLPG) values('2 - BCD - 234', '0');
insert into MOTORCYCLE(LICENSEPLATE, HASSIDECAR) values('M - ABC - 123', '0');

Интуитивно вы можете захотеть найти Транспортное средство с номерным знаком 1 - ABC - 123 :

assertThrows(Exception.class, () -> entityManager.find(Vehicle.class, "1 - ABC - 123"));

И это вызовет исключение. Не существует сохраненных Транспортных средств сущностей. Однако существуют сохраненные Car сущности. Давайте поищем Машину с этим номерным знаком:

Car foundCar = entityManager.find(Car.class, "1 - ABC - 123");

assertThat(foundCar).isNotNull();
assertThat(foundCar.licensePlate()).isEqualTo("1 - ABC - 123");
assertThat(foundCar.runOnLpg()).isTrue();

Стратегия с Одним Столом

Давайте теперь перейдем к стратегии С одной таблицей . Эта стратегия позволяет нам сопоставлять все сущности иерархии классов с одной и той же таблицей базы данных.

Если мы повторно используем наш пример парковки, это будет означать, что все автомобили и мотоциклы будут сохранены в таблице ТРАНСПОРТНОЕ СРЕДСТВО .

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

  • @Наследование – определяет стратегию наследования и используется для всех стратегий, кроме сопоставленных суперклассов.
  • @DiscriminatorColumn – который определяет столбец, целью которого будет определение того, какая сущность сохранена в данной строке базы данных. Мы пометим это как ТИП , обозначая тип транспортного средства.
  • @DiscriminatorValue – который определяет значение столбца дискриминатора для данной сущности – таким образом, является ли данная сущность Автомобилем или Мотоциклом .

На этот раз Транспортное средство является управляемым JPA @Сущностью , так как мы сохраняем его в таблице. Давайте также добавим к нему @Inheritance и @DiscriminatorColumn аннотации:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")
public class Vehicle {
    @Id
    private String licensePlate;
}

Аннотация @Наследование принимает флаг стратегия , который мы установили в Тип наследования.SINGLE_TABLE . Это позволяет JPA знать, что мы выбрали подход с одной таблицей. Этот тип также является типом по умолчанию, поэтому, даже если бы мы не указали никаких стратегий, он все равно был бы SINGLE_TABLE .

Мы также установили имя столбца дискриминатора TYPE (по умолчанию используется DTYPE ). Теперь, когда JPA генерирует таблицы, это будет выглядеть так:

create table Vehicle (TYPE varchar(31) not null, licensePlate varchar(255) not null, hasSideCar boolean, runOnLpg boolean, primary key (licensePlate))

Это имеет несколько последствий:

  • Поля для Автомобиля и Мотоцикла хранятся в одной таблице , что может привести к беспорядку, если у нас много полей.
  • Поля всех подклассов должны быть обнуляемыми (потому что Автомобиль не может иметь значений для полей Мотоцикл и наоборот), что означает меньшую проверку на уровне базы данных.

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Тем не менее, давайте нанесем на карту наш Автомобиль и Мотоцикл прямо сейчас:

@Entity
@DiscriminatorValue("C")
class Car extends Vehicle {
    private boolean runOnLpg;
}

@Entity
@DiscriminatorValue("M")
class Motorcycle extends Vehicle {
    private boolean hasSideCar;
}

Здесь мы определяем значения столбца дискриминатора для наших сущностей. Мы выбрали C для автомобилей и M для мотоциклов. По умолчанию JPA использует имя сущностей. В нашем случае Автомобиль и Мотоцикл соответственно.

Теперь давайте добавим несколько транспортных средств и посмотрим, как EntityManager справляется с ними:

insert into VEHICLE(LICENSEPLATE, TYPE, RUNONLPG, HASSIDECAR) values('1 - ABC - 123', 'C', '1', null);
insert into VEHICLE(LICENSEPLATE, TYPE, RUNONLPG, HASSIDECAR) values('2 - BCD - 234', 'C', '0', null);
insert into VEHICLE(LICENSEPLATE, TYPE, RUNONLPG, HASSIDECAR) values('M - ABC - 123', 'M', null, '0');

С одной стороны, мы можем получить каждый Автомобиль или Мотоцикл объект:

Car foundCar = entityManager.find(Car.class, "1 - ABC - 123");

assertThat(foundCar).isNotNull();
assertThat(foundCar.licensePlate()).isEqualTo("1 - ABC - 123");
assertThat(foundCar.runOnLpg()).isTrue();

Но, поскольку Транспортное средство также является сущностью, мы также можем извлекать сущности в качестве их суперкласса – Транспортное средство :

Vehicle foundCar = entityManager.find(Vehicle.class, "1 - ABC - 123");

assertThat(foundCar).isNotNull();
assertThat(foundCar.licensePlate()).isEqualTo("1 - ABC - 123");

На самом деле, мы даже можем спасти Транспортное средство объект, который не является ни Автомобилем , ни Мотоциклом :

Vehicle vehicle = new Vehicle();
vehicle.setLicensePlate("T - ABC - 123");

entityManager.persist(vehicle);

Что переводится в следующий SQL-запрос:

insert into Vehicle (TYPE, licensePlate) values ('Vehicle', ?)

Хотя мы, возможно, и не хотели бы, чтобы это произошло – мы должны использовать аннотацию @Entity на транспортном средстве в этой стратегии.

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

Одна Таблица На Стратегию Класса

Следующая стратегия называется Одна таблица на класс , которая, как следует из названия, создает одну таблицу на класс в иерархии .

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

Этот подход очень похож на подход MappedSuperclass – с той лишь разницей, что суперкласс также является сущностью .

Чтобы ВЫ знали, что мы хотели бы применить эту стратегию, мы установим Тип наследования в TABLE_PER_CLASS в нашей @Наследование аннотации:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
    @Id
    private String licensePlate;
}

Наши Автомобильные и Мотоциклетные классы просто должны быть сопоставлены с помощью @Entity , и мы закончили. Определения таблиц такие же, как и в mappedsuperclass, плюс таблица ТРАНСПОРТНОЕ СРЕДСТВО (потому что это конкретный класс).

Но что отличается от mappedsuperclass, так это то, что мы можем искать Транспортное средство объект, а также Автомобиль или Мотоцикл объект:

Vehicle foundVehicle = entityManager.find(Vehicle.class, "1 - ABC - 123");
Car foundCar = entityManager.find(Car.class, "1 - ABC - 123");

assertThat(foundVehicle).isNotNull();
assertThat(foundVehicle.licensePlate()).isEqualTo("1 - ABC - 123");
assertThat(foundCar).isNotNull();
assertThat(foundCar.licensePlate()).isEqualTo("1 - ABC - 123");
assertThat(foundCar.runOnLpg()).isTrue();

Стратегия Объединенного Стола

Наконец, есть Объединенная таблица стратегия. Он создает одну таблицу для каждой сущности и сохраняет каждый столбец там, где он, естественно, принадлежит.

Давайте возьмем нашу Личность |/Ученика Учителя иерархию. Если мы реализуем его с помощью стратегии объединенных таблиц, мы получим три таблицы:

create table Person (id bigint not null, city varchar(255), number varchar(255), street varchar(255), birthDate date, FIRST_NAME varchar(255), gender varchar(255), lastName varchar(255), primary key (id))
create table STUD (wantsNewsletter boolean not null, id bigint not null, primary key (id))
create table Teacher (id bigint not null, primary key (id))

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

При поиске студента JPA выдаст SQL-запрос с соединением между таблицами STUD и PERSON , чтобы получить все данные студента.

Чтобы отобразить эту иерархию, мы будем использовать тип наследование.ПРИСОЕДИНИЛСЯ стратегия, в @Наследование аннотация:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String lastName;

    @Column(name = "FIRST_NAME")
    private String firstName;

    private LocalDate birthDate;

    @Enumerated(EnumType.STRING)
    private Student.Gender gender;

    @Embedded
    private Address address;
}

Наши другие сущности просто отображаются с помощью @Entity :

@Entity
public class Student extends Person {
    @Id
    private Long id;
    private boolean wantsNewsletter;
    private Gender gender;
}

И:

@Entity
public class Teacher extends Person {
    @Id
    private Long id;

Давайте также определим ПЕРЕЧИСЛЕНИЕ, которое мы использовали в классе Студент :

enum GENDER {
MALE, FEMALE
}

Итак, мы можем извлечь Человека , Ученика и Учителя сущности, а также сохранить их с помощью EntityManager.persist() .

Опять же, если мы хотим избежать создания Лиц сущностей, мы должны сделать это абстрактным .

Вывод

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

Код для этой серии можно найти на GitHub .