Автор оригинала: 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 .