«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
}
Затем мы добавим сущность Allergens: внешний ключ. Это означает, что нам нужно определить столбец отношения один к одному, используя @PrimaryKeyJoinColumn.
@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
}
Однако у этого решения есть две проблемы:
Мы всегда хотим хранить аллергены для еды, а это решение не соблюдает это правило. Данные о еде и аллергенах логически связаны друг с другом, поэтому мы можем эта информация находится в том же классе Java, хотя мы создали для них несколько таблиц
-
Одним из возможных решений первой проблемы является добавление аннотации @NotNull к полю аллергенов в объекте Meal. JPA не позволит нам продолжать прием пищи, если у нас нет аллергенов.
Однако это не идеальное решение; мы хотим более ограничительного, где у нас даже нет возможности попытаться настоять на еде без аллергенов.
4. Создание единого объекта с помощью @SecondaryTable
Мы можем создать один объект, указав, что у нас есть столбцы в разных таблицах, используя аннотацию @SecondaryTable:
За кулисами JPA присоединяется к основной таблице со вторичной таблицей и заполняет поля. Это решение похоже на отношение @OneToOne, но таким образом мы можем иметь все свойства в одном классе.
@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
}
Важно отметить, что если у нас есть столбец, который находится во вторичной таблице, мы должны указать его с помощью аргумента таблицы аннотации @Column. Если столбец находится в основной таблице, мы можем опустить аргумент таблицы, так как JPA по умолчанию ищет столбцы в основной таблице.
Также обратите внимание, что у нас может быть несколько вторичных таблиц, если мы встроим их в @SecondaryTables. В качестве альтернативы, начиная с Java 8, мы можем пометить объект несколькими аннотациями @SecondaryTable, поскольку это повторяемая аннотация.
5. Объединение @SecondaryTable с @Embedded
Как мы видели, @SecondaryTable сопоставляет несколько таблиц одному и тому же объекту. Мы также знаем, что @Embedded и @Embeddable делают обратное и сопоставляют одну таблицу нескольким классам.
Давайте посмотрим, что мы получим, если объединим @SecondaryTable с @Embedded и @Embeddable:
Это тот же подход, что и при использовании @OneToOne. Тем не менее, у него есть несколько преимуществ:
@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
}
JPA управляет двумя таблицами вместе, поэтому мы можем быть уверены, что для каждого приема пищи в обеих таблицах будет строка. Кроме того, код немного проще, поскольку нам нужно меньше конфигурации
-
Тем не менее, это решение типа «один к одному» работает только тогда, когда две таблицы имеют совпадающие идентификаторы.
Стоит отметить, что если мы хотим повторно использовать класс Allergens, было бы лучше, если бы мы определили столбцы вторичной таблицы в классе Meal с помощью @AttributeOverride.
6. Заключение
«В этом кратком руководстве мы увидели, как можно сопоставить несколько таблиц с одним и тем же объектом, используя аннотацию @SecondaryTable JPA.
Мы также увидели преимущества комбинирования @SecondaryTable с @Embedded и @Embeddable для получения связи, похожей на один к одному.
Как обычно, примеры доступны на GitHub.
«