Рубрики
Без рубрики

Отношения “Один к одному” в JPA

Изучите три различных способа поддержания отношений “один к одному” с JPA.

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы рассмотрим различные способы создания взаимно однозначных сопоставлений в JPA.

Нам понадобится базовое понимание фреймворка Hibernate, поэтому, пожалуйста, ознакомьтесь с нашим руководством по Hibernate 5 с Spring для получения дополнительной информации.

Дальнейшее чтение:

Обзор типов каскадов JPA/Hibernate

Спящий режим Один ко многим Учебник по аннотациям

2. Описание

Предположим, мы создаем систему управления пользователями, и наш босс просит нас сохранить почтовый адрес для каждого пользователя. У пользователя будет один почтовый адрес, а к почтовому адресу будет привязан только один пользователь.

Это пример отношения “один к одному”, в данном случае между сущностями user и address .

Давайте посмотрим, как мы можем реализовать это в следующих разделах.

3. Использование внешнего ключа

3.1. Моделирование с помощью Внешнего ключа

Давайте взглянем на следующую диаграмму ER , которая представляет собой взаимно однозначное сопоставление на основе внешнего ключа:

Диаграмма ER, сопоставляющая пользователей с адресами с помощью внешнего ключа address_id

В этом примере столбец address_id в users является внешним ключом к адресу .

3.2. Реализация с внешним ключом в JPA

Во-первых, давайте создадим класс User и соответствующим образом аннотируем его:

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    //... 

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

    // ... getters and setters
}

Обратите внимание, что мы помещаем @OneToOne аннотацию в поле связанной сущности Адрес .

Кроме того, нам нужно поместить аннотацию @JoinColumn , чтобы настроить имя столбца в таблице users , которое сопоставляется с первичным ключом в таблице address . Если мы не предоставим имя, Hibernate будет следовать некоторым правилам , чтобы выбрать имя по умолчанию.

Наконец, обратите внимание в следующей сущности, что мы не будем использовать там аннотацию @JoinColumn . Это происходит потому, что нам это нужно только на стороне владения отношениями внешнего ключа. Проще говоря, тот, кому принадлежит столбец внешнего ключа, получает аннотацию @JoinColumn .

Сущность Address оказывается немного проще:

@Entity
@Table(name = "address")
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    //...

    @OneToOne(mappedBy = "address")
    private User user;

    //... getters and setters
}

Нам также нужно разместить здесь аннотацию @OneToOne . Это потому, что это двунаправленная связь . Адресная сторона отношений называется стороной, не являющейся владельцем.

4. Использование общего первичного ключа

4.1. Моделирование С общим первичным ключом

В этой стратегии вместо создания нового столбца address_id , мы пометим первичный ключ | столбец ( user_id ) таблицы address в качестве внешнего ключа для таблицы users :

Диаграмма ER с пользователями, привязанными к адресам, где они используют одни и те же значения первичного ключа

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

4.2. Реализация с общим первичным ключом в JPA

Обратите внимание, что наши определения меняются лишь незначительно:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private Address address;

    //... getters and setters
}
@Entity
@Table(name = "address")
public class Address {

    @Id
    @Column(name = "user_id")
    private Long id;

    //...

    @OneToOne
    @MapsId
    @JoinColumn(name = "user_id")
    private User user;
   
    //... getters and setters
}

Атрибут mappedBy теперь перемещен в класс User , так как внешний ключ теперь присутствует в таблице address . Мы также добавили аннотацию @PrimaryKeyJoinColumn , которая указывает, что первичный ключ сущности User используется в качестве значения внешнего ключа для связанной сущности Address .

Нам все еще нужно определить поле @Id в классе Address . Но обратите внимание, что это ссылается на столбец user_id , и он больше не использует аннотацию @GeneratedValue . Кроме того, в поле , которое ссылается на User , мы добавили аннотацию @MapsId , которая указывает, что значения первичного ключа будут скопированы из сущности User .

5. Использование таблицы соединений

Взаимно однозначные сопоставления могут быть двух типов: необязательные и обязательные. До сих пор мы видели только обязательные отношения.

Теперь давайте представим, что наши сотрудники связаны с рабочей станцией. Это один к одному, но иногда у сотрудника может не быть рабочей станции, и наоборот.

5.1. Моделирование с помощью таблицы соединений

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

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

Диаграмма ER, связывающая сотрудников с рабочими станциями через объединенную таблицу

Теперь, когда у нас есть отношения, мы будем делать запись в таблице emp_workstation и вообще избегать нулей .

5.2. Реализация с помощью таблицы соединений в JPA

Наш первый пример использовал @JoinColumn . На этот раз мы будем использовать @JoinTable :

@Entity
@Table(name = "employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "emp_workstation", 
      joinColumns = 
        { @JoinColumn(name = "employee_id", referencedColumnName = "id") },
      inverseJoinColumns = 
        { @JoinColumn(name = "workstation_id", referencedColumnName = "id") })
    private WorkStation workStation;

    //... getters and setters
}
@Entity
@Table(name = "workstation")
public class WorkStation {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(mappedBy = "workStation")
    private Employee employee;

    //... getters and setters
}

@ JoinTable инструктирует Hibernate использовать стратегию объединения таблиц при сохранении отношений.

Кроме того, Employee является владельцем этой связи, поскольку мы решили использовать аннотацию таблицы соединений для нее.

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

В этой статье мы узнали о различных способах поддержания связи “один к одному” в JPA и Hibernate, а также о том, когда использовать каждый из них.

Исходный код этой статьи можно найти на GitHub .