Съставяне на единичен обект на множество таблици в JPA

Устойчивост отгоре

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Въведение

JPA прави работата с модели на релационни бази данни от нашите Java приложения по-малко болезнена. Нещата са прости, когато съпоставяме всяка таблица с отделен клас на обект. Но понякога имаме причини да моделираме нашите обекти и таблици по различен начин:

  • Когато искаме да създадем логически групи полета, можем да съпоставим множество класове в една таблица
  • Ако е включено наследяване, можем да картографираме йерархия на класа към структура на таблица
  • В случаите, когато свързани полета са разпръснати между множество таблици и ние искаме да ги моделираме с един клас

В този кратък урок ще видим как да се справим с този последен сценарий.

2. Модел на данни

Да предположим, че управляваме ресторант и искаме да съхраняваме данни за всяко хранене, което сервираме:

  • име
  • описание
  • цена
  • какъв вид алергени съдържа

Тъй като има много възможни алергени, ще групираме този набор от данни заедно. Освен това ще моделираме това, като използваме следните дефиниции на таблици:

Сега нека видим как можем да съпоставим тези таблици с обекти, използващи стандартни JPA анотации.

3. Създаване на множество обекти

Най-очевидното решение е да се създаде обект и за двата класа.

Нека започнем с дефиниране на обекта на хранене :

@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 няма да ни позволи да продължим храненето, ако имаме нулеви алергени .

Това обаче не е идеалното решение; искаме по-ограничителен, при който дори нямаме възможност да се опитаме да продължим да храним без алергени.

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 , но по този начин можем да имаме всички свойства в същия клас.

Важно е да се отбележи, че ако имаме колона, която е във вторична таблица, трябва да я посочим с аргумента на таблицата на анотацията @Column . Ако колона е в първичната таблица, можем да пропуснем аргумента на таблицата , тъй като JPA по подразбиране търси колони в първичната таблица.

Също така имайте предвид, че можем да имаме множество вторични таблици, ако ги вградим в @SecondaryTables . Като алтернатива, от Java 8 можем да маркираме обекта с множество @SecondaryTable анотации, тъй като това е повторяема анотация.

5. Комбиниране на @SecondaryTable с @Embedded

Както видяхме, @SecondaryTable съпоставя множество таблици към една и съща същност. Знаем също така, че @Embedded и @ вграждане да правят точно обратното и картата една таблица с няколко класи.

Нека видим какво получаваме, когато комбинираме @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 управлява двете маси заедно за нас, така че можем да бъдем сигурни, че ще има ред за всяко хранене и в двете маси
  • Също така кодът е малко по-опростен, тъй като се нуждаем от по-малко конфигурация

Независимо от това, това едно към едно подобно решение работи само когато двете таблици имат съвпадащи идентификатори.

Струва си да се спомене, че ако искаме да използваме повторно класа на алергените , би било по-добре, ако дефинираме колоните на вторичната таблица в класа Meal с @AttributeOverride .

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

В този кратък урок видяхме как можем да съпоставим множество таблици към един и същ обект, като използваме анотацията @SecondaryTable JPA.

Видяхме и предимствата от комбинирането на @SecondaryTable с @Embedded и @Embeddable, за да получите връзка, подобна на едно към едно.

Както обикновено, примерите са достъпни в GitHub.

Устойчивост отдолу

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА