Перейти к основному содержимому

Сопоставление одного объекта с несколькими таблицами в JPA

· 5 мин. чтения

1. Введение

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

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

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

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

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

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

./46fb36ae11dbe99eff7e75c3b4c26f3c.png

Теперь давайте посмотрим, как мы можем сопоставить эти таблицы с сущностями, используя стандартные аннотации 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
}

Далее мы добавим объект Allergens :

@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
}

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

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

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

Одним из возможных решений первой проблемы является добавление аннотации @NotNull к полю аллергенов в объекте Meal . 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 и @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 управляет двумя таблицами вместе для нас, поэтому мы можем быть уверены, что в обеих таблицах будет строка для каждого приема пищи.
  • Кроме того, код немного проще, так как нам нужно меньше настроек.

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

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

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

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

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

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