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

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

1. Вступление

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

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

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

Обзор JPA / Hibernate Типы Каскадов

Быстрый и практический обзор типов каскада JPA / Hibernate.

Read more

Hibernate Учебник по аннотации ко многим

В этом руководстве мы рассмотрим сопоставление "один ко многим" с использованием аннотаций JPA на практическом примере.

Read more

2. Описание

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

Это пример взаимно-однозначного отношения, в данном случае между значениямиuser иaddress .

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

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

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

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

An ER Diagram mapping Users to Addresses via an address_id foreign key

В этом примере столбецaddress_id вusers - это отforeign key доaddress.

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
}

Обратите внимание, чтоwe place the @OneToOne annotation в поле связанной сущностиAddress.

Кроме того,we need to place the @JoinColumn annotation позволяет настроить имя столбца в таблицеusers, который сопоставляется с первичным ключом в таблицеaddress. Если мы не укажем имя, Hibernate будетfollow some rules, чтобы выбрать имя по умолчанию.

Наконец, обратите внимание, что в следующем объекте мы не будем использовать там саннотацию@JoinColumn . Это потому, что он нам нужен только на сторонеowning  отношения внешнего ключа. Simply put, whoever owns the foreign key column gets the @JoinColumn annotation.

Посылка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 . Это потому, что этоbidirectional relationship. The address side of the relationship is called the non-owning side. 

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

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

В этой стратегии вместо создания нового столбцаaddress_id, we’ll mark the primary key column (user_id) of the address table as the foreign key to the users table:

An ER diagram with Users Tied to Addresses where they share the same primary key values

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

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)
    private Address address;

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

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

    //...

    @OneToOne
    @MapsId
    private User user;

    //... getters and setters
}

@MapsId tells Hibernate to use the id column of address as both primary key and foreign key. Также обратите внимание, чтоthe @Id column of the Address entity no longer uses the @GeneratedValue annotation.

АтрибутmappedBy теперь перемещен в классUser , поскольку внешний ключ теперь присутствует в стабильномaddress .

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

Сопоставления «один-к-одному» могут быть двух типов -Optional иMandatory. So far, we’ve seen only mandatory relationships.

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

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

Обсуждаемые нами стратегии until now force us to put null values in the column to handle optional relationships.

Обычно мы думаем оmany-to-many relationships, когда рассматриваем таблицу соединений,but, using a join table, in this case, can help us to eliminate these null values:

An ER diagram relating Employees to Workstations via a Join Table

Теперь, когда у нас будет связь, мы сделаем запись в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 и о том, когда их использовать.

Исходный код этого руководства можно найтиover on GitHub.