Отношения один-к-одному в JPA
1. Вступление
В этом руководстве мы рассмотрим различные способы создания однозначных сопоставлений в JPA.
Нам потребуется базовое понимание структуры Hibernate, поэтому, пожалуйста, ознакомьтесь с нашимиGuide to Hibernate 5 with Spring для получения дополнительной информации.
Дальнейшее чтение:
Обзор JPA / Hibernate Типы Каскадов
Быстрый и практический обзор типов каскада JPA / Hibernate.
Hibernate Учебник по аннотации ко многим
В этом руководстве мы рассмотрим сопоставление "один ко многим" с использованием аннотаций JPA на практическом примере.
2. Описание
Предположим, мы создаем систему управления пользователями, и наш начальник просит нас сохранить почтовый адрес для каждого пользователя. У пользователя будет один почтовый адрес, а к почтовому адресу будет привязан только один пользователь.
Это пример взаимно-однозначного отношения, в данном случае между значениямиuser иaddress .
Давайте посмотрим, как мы можем это реализовать, в следующих разделах.
3. Использование внешнего ключа
3.1. Моделирование с помощью внешнего ключа
Давайте посмотрим на следующийER diagram, который представляет собой взаимно-однозначное сопоставление на основе внешнего ключа:
В этом примере столбец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:
Мы оптимизировали пространство для хранения, используя тот факт, что между этими объектами существует взаимно однозначная связь.
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:
Теперь, когда у нас будет связь, мы сделаем запись в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.