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

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

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

1. Обзор

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

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

2. Описание

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

Это пример связи «один к одному», в данном случае между объектами пользователя и адреса .

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

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

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

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

./7b6287f6d3c23a864cb84d74bcf4fc27.png

В этом примере столбец address_id в users является внешним ключом для 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
}

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

Кроме того, нам нужно разместить аннотацию @JoinColumn , чтобы настроить имя столбца в таблице пользователей , которое сопоставляется с первичным ключом в таблице адресов . Если мы не укажем имя, 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 ) таблицы адресов как внешний ключ для таблицы пользователей` :

./44c9cf2f4a36c32752acc4ab63ef1980.png

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

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 , так как внешний ключ теперь присутствует в таблице адресов . Мы также добавили аннотацию @PrimaryKeyJoinColumn , указывающую, что первичный ключ сущности User используется в качестве значения внешнего ключа для связанной сущности Address .

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

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

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

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

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

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

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

./488ac094213d3a6a72e9e88b793f4644.png

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