«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.

«