2.1 Связь на уровне таблиц

Мы познакомились с тем, как Hibernate хранит коллекции во вспомогательных таблицах. Теперь же разберемся, как организовать связи между полноценными таблицами, которые хранят настоящие Entity-классы.

Всего между Entity-классами в Hibernate есть четыре вида отношений:

  • one-to-one
  • one-to-many
  • many-to-one
  • many-to-many

И начнем мы разбор с самого простого варианта – many-to-one.

С таким отношением между таблицами в SQL ты уже сталкивался. Вот как оно обычно выглядит:

id name occupation salary age join_date
1 Иванов Иван Программист 100000 25 2012-06-30
2 Петров Петр Программист 80000 23 2013-08-12
3 Иванов Сергей Тестировщик 40000 30 2014-01-01
4 Рабинович Мойша Директор 200000 35 2015-05-12
5 Кириенко Анастасия Офис-менеджер 40000 25 2015-10-10
6 Васька Кот 1000 3 2018-11-11

Таблица employee:

В этой таблице есть такие колонки:

  • id INT
  • name VARCHAR
  • occupation VARCHAR
  • salary INT
  • age INT
  • join_date DATE

А так выглядит таблица task, которая содержит задачи для сотрудников:

id employee_id name deadline
1 1 Исправить багу на фронтенде 2022-06-01
2 2 Исправить багу на бэкенде 2022-06-15
3 5 Купить кофе 2022-07-01
4 5 Купить кофе 2022-08-01
5 5 Купить кофе 2022-09-01
6 (NULL) Убрать офис (NULL)
7 4 Наслаждаться жизнью (NULL)
8 6 Наслаждаться жизнью (NULL)

В этой таблице есть всего 4 колонки:

  • id – уникальный номер задания (и строки в таблице);
  • employee_id – ID сотрудника из таблицы employee, на которого назначена задача;
  • name – название и описание задачи;
  • deadline – время до которого нужно выполнить задачу.

Мы видим, что много строк таблицы task могут ссылаться на одну запись таблицы employee. Такая связь на уровне таблиц называется многие-к-одному (many-to-one).

2.2 Связь на уровень Java-классов

Кроме связи на уровне таблиц, можно еще организовать связь на уровне Entity-классов в Hibernate. Делается это с помощью аннотации @ManyToOne.

Но для начала просто создадим два класса: Employee и EmployeeTask:


@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @Column(name="name")
   public String name;
 
   @Column(name="occupation")
   public String occupation;
 
   @Column(name="salary")
   public Integer salary;
 
   @Column(name="join_date")
   public Date join;
}

И второй класс для хранения заданий сотрудников:


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

С этими классами все хорошо, но между ними нет никакой связи, которая бы отражала тот факт, что поле employeeId класса EmployeeTask ссылается на поле id класса Employee. Пришло время это исправить

2.3 Аннотация @ManyToOne.

Во-первых, в Java мы привыкли оперировать объектами (и ссылками на объекты), а не их id. Так что первым делом давайте вместо поля employeeId в классе EmployeeTask просто укажем ссылку на объект типа Employee. Вот как будет выглядеть наш новый класс:


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

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

2.4 Примеры запросов

А теперь покажем несколько примеров, как Hibernate умеет работать с такими связанными классами.

Сценарий первый

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

from EmployeeTask where employee.name = "Иван Иванович"

Ты можешь просто обращаться к полям зависимых классов через точку. Это очень удобно. Но давай все же запишем этот запрос в виде Java-кода:


String hql = "from EmployeeTask where employee.name = :username";
Query<EmployeeTask> query = session.createQuery( hql, EmployeeTask.class);
query.setParameter("username", "Иван Иванович");
List<EmployeeTask> resultLIst = query.list();

Сценарий второй

Давай напишем запрос, который возвращает список сотрудников, у которых есть просроченные задачи. Задача просрочена, если ее deadline уже в прошлом. Вот как будет выглядеть этот запрос на SQL:


SELECT DISTINCT employee.*
FROM task JOIN employee ON task.employee_id = employee.id
WHERE task.deadline < CURDATE();

DISTINCT используется, потому что может быть много задач назначенных на одного пользователя.

А теперь запишем этот же запрос на HQL:

select distinct employee from EmployeeTask where deadline < CURDATE();

Employee в этом запросе – это поле класса EmployeeTask

Ситуация третья

Назначим все неназначенные задачи на директора. Запрос на SQL будет выглядеть так:


UPDATE task SET employee_id = 4 WHERE employee_id IS NULL

А теперь запишем этот же запрос на HQL:

update EmployeeTask set employee = :user where employee is null

Последний запрос самый сложный. Нам нужно передать ID директора, но класс EmployeeTask не содержит поля, куда можно записать id, вместо этого он содержит поле Employee, куда нужно присвоить ссылку на объект типа Employee.


Employee director = session.get(Employee.class, 4);
 
String hql = "update EmployeeTask set employee = :user where employee is null";
Query<EmployeeTask> query = session.createQuery(hql, EmployeeTask.class);
query.setParameter("user", director);
query.executeUpdate();

undefined
1
Задача
Модуль 4. Работа с БД, 13 уровень, 1 лекция
Недоступна
Опять книги
Есть три класса-энтити: Author, Book и Publisher, и таблицы, которые им соответствуют