1. Різні форми зв'язку один до одного

Є ще один цікавий і досить специфічний випадок відносин між двома Entity-класами — це відношення один-до-одного.

Я називаю цей випадок дуже специфічним, оскільки це більше про Java-об'єкти, ніж про базу даних. У базі даних є лише два варіанти зв'язку між таблицями:

  • Рядок таблиці містить посилання на id іншої таблиці.
  • Службова таблиця використовується для зв'язку many-to-many.

У випадку з Entity-класами можуть бути варіанти, які описуються кількома анотаціями:

  • @Embedded
  • Односторонній OneToOne
  • Двосторонній OneToOne
  • @MapsId

Нижче ми розглянемо найпопулярніші з них.

2. @Embedded

Найпростіший варіант зв'язку one-to-one ми, до речі, вже розглянули — це анотація @Embedded. У цьому випадку у нас два класи зберігаються в одній таблиці.

Припустимо, ми хочемо зберігати адресу користувача в класі UserAddress:


@Embeddable
class UserAddress {
   @Column(name="user_address_country")
   public String country;
   @Column(name="user_address_city")
   public String city;
   @Column(name="user_address_street")
   public String street;
   @Column(name="user_address_home")
   public String home;
}

Тоді нам потрібно просто додати поле з цією адресою до класу User:


@Entity
@Table(name="user")
class User {
   @Column(name="id")
   public Integer id;
 
   @Embedded
   public UserAddress address;
 
   @Column(name="created_date")
   public Date createdDate;
}

Все інше зробить Hibernate: дані зберігатимуться в одній таблиці, але під час написання HQL-запитів тобі потрібно буде оперувати саме полями класів.

Приклад HQL-запиту:

select from User where address.city = 'Paris'

3. Односторонній OneToOne

Уявимо тепер ситуацію: у нас є вихідна таблиця employee і task, який посилається на employee. Але ми точно знаємо, що на одного користувача може бути призначено максимум одне завдання. Тоді для опису цієї ситуації ми можемо скористатися анотацією @OneToOne.

Приклад:


@Entity
@Table(name="task")
class EmployeeTask {
   @Column(name="id")
   public Integer id;
 
   @Column(name="name")
   public String description;
 
   @OneToOne
   @JoinColumn(name = "employee_id")
   public Employee employee;
 
   @Column(name="deadline")
   public Date deadline;
}

Hibernate стежитиме за тим, щоб не тільки в одного завдання був один користувач, а й щоб у одного користувача було лише одне завдання. Загалом цей випадок практично нічим не відрізняється від @ManyToOne.

4. Двосторонній OneToOne

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

Для цього можна додати поле EmployeeTask до класу Employee та виставити для нього правильні анотації.


@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToOne(cascade = CascadeType.ALL, mappedBy="employee")
   private EmployeeTask task;
}

Важливо! У таблиці employee немає поля task_id, натомість для встановлення зв'язку між таблицями використовується поле employee_id таблиці task.

Встановлення зв'язку між об'єктами виглядає так:


Employee director = session.find(Employee .class, 4);
EmployeeTask task = session.find(EmployeeTask . class, 101);
task.employee = director;
director.task = task;
 
session.update(task);
session.flush();

Для видалення зв'язку посилання також потрібно видалити в обох об'єктів:


Employee director = session.find(Employee .class, 4);
EmployeeTask task = director.task;
 
task.employee = null;
session.update(task);
 
director.task = null;
session.update(director);
 
session.flush();