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

Сопоставление одной сущности с несколькими таблицами в JPA

Узнайте, как сопоставить одну сущность Java с несколькими таблицами базы данных с помощью JPA.

Автор оригинала: Attila Fejér.

1. введение

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

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

В этом коротком уроке мы увидим, как справиться с этим последним сценарием.

2. Модель данных

Допустим, у нас есть ресторан, и мы хотим хранить данные о каждом приеме пищи, который мы подаем:

  • имя
  • описание
  • цена
  • какие аллергены он содержит

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

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

3. Создание Нескольких Сущностей

Наиболее очевидным решением является создание сущности для обоих классов.

Давайте начнем с определения сущности Meal :

@Entity
@Table(name = "meal")
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @OneToOne(mappedBy = "meal")
    Allergens allergens;

    // standard getters and setters
}

Далее мы добавим объект Аллергены :

@Entity
@Table(name = "allergens")
class Allergens {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "meal_id")
    Long mealId;

    @OneToOne
    @PrimaryKeyJoinColumn(name = "meal_id")
    Meal meal;

    @Column(name = "peanuts")
    boolean peanuts;

    @Column(name = "celery")
    boolean celery;

    @Column(name = "sesame_seeds")
    boolean sesameSeeds;

    // standard getters and setters
}

В приведенном выше примере мы видим, что meal_id является как первичным ключом, так и внешним ключом. Это означает, что нам нужно определить столбец отношений “один к одному” с помощью @PrimaryKeyJoinColumn .

Однако у этого решения есть две проблемы:

  • Мы всегда хотим хранить аллергены для еды, и это решение не обеспечивает соблюдение этого правила
  • Данные о еде и аллергенах логически связаны друг с другом – поэтому мы можем захотеть сохранить эту информацию в одном классе Java, даже если мы создали для них несколько таблиц

Одним из возможных решений первой проблемы является добавление аннотации @NotNull в поле аллергены нашей сущности Еда . JPA не позволит нам сохранить Еду , если у нас есть null Аллергены .

Однако это не идеальное решение; мы хотим более строгое, когда у нас даже нет возможности попытаться сохранить Еду без аллергенов.

4. Создание единого объекта с помощью @SecondaryTable

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

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

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

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

Кроме того, обратите внимание, что у нас может быть несколько вторичных таблиц, если мы встроим их в @SecondaryTables . В качестве альтернативы, начиная с Java 8, мы можем пометить объект несколькими аннотациями @SecondaryTable , поскольку это повторяемая аннотация .

5. Объединение @SecondaryTable С @Embedded

Как мы уже видели, @SecondaryTable сопоставляет несколько таблиц с одной и той же сущностью. Мы также знаем, что @Embedded и @ Embeddable делают обратное и сопоставляют одну таблицу нескольким классам .

Давайте посмотрим, что мы получим, когда объединим @SecondaryTable с @Embedded и @Embeddable :

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Embedded
    Allergens allergens;

    // standard getters and setters

}

@Embeddable
class Allergens {

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

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

  • JPA управляет двумя столами вместе для нас, поэтому мы можем быть уверены, что для каждого приема пищи в обоих столах будет строка
  • Кроме того, код немного проще, так как нам нужно меньше конфигурации

Тем не менее, это решение типа “один к одному” работает только тогда, когда две таблицы имеют совпадающие идентификаторы.

Стоит отметить, что если мы хотим уменьшить класс Аллергены , было бы лучше, если бы мы определили столбцы вторичной таблицы в классе Еда с помощью @AttributeOverride .

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

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

Мы также увидели преимущества объединения @SecondaryTable с @Embedded и @Embeddable , чтобы получить отношения, аналогичные взаимно однозначным.

Как обычно, примеры доступны на GitHub .