@ManyToMany

Модуль 4. Работа с БД
13 уровень , 3 лекция
Открыта

Служебная таблица

Теперь разберем еще один часто встречающийся случай – many-to-many. Давай представим, что у нас отношение между задачами и сотрудниками многие-ко-многим:

  • Один сотрудник в таблице employee может делать много задач из таблицы task.
  • Одна задача в таблице task может быть назначена на несколько сотрудников.

Такая связь между сущностями называется многие-ко-многим. И чтобы ее реализовать на уровне SQL, нам понадобится дополнительная служебная таблица. Назовем ее, например, employee_task.

Таблица employee_task будет содержать всего две колонки:

  • employee_id
  • task_id

Каждый раз, когда мы будем назначать определенную задачу определенному пользователю, в эту таблицу будет добавляться новая строка. Пример:

employee_id task_id
1 1
1 2
2 3

Ну, а таблица task должна лишиться колонки employee_id. В ней есть смысл, только если задача может быть назначена только на одного сотрудника. Если же задача может быть назначена на нескольких сотрудников, то эту информацию нужно хранить в служебной таблице employee_task.

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

Вот как будут выглядеть наши новые таблицы:

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, потеряла колонку employee_id (отмечена красным):

id emploee_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)

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

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

Также у нас есть служебная таблица employee_task, куда перекочевали данные об employee_id из таблицы task:

employee_id task_id
1 1
2 2
5 3
5 4
5 5
(NULL) 6
4 7
6 8

Я специально временно сохранил удаленную колонку в таблице task, чтобы ты мог увидеть, что данные из нее переехали в таблицу employee_task.

Еще один важный момент – красная строка "(NULL) 6" в таблице employee_task. Я отметил ее красным, так как ее не будет в таблице employee_task.

Если таск 7 назначен на пользователя 4, то в таблице employee_task должна быть строка (4, 7).

Если таск 6 ни на кого не назначен, то просто в таблице employee_task для него не будет никакой записи. Вот как будут выглядеть финальные версии этих таблиц:

Таблица task:

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

Tаблица employee_task:

employee_id task_id
1 1
2 2
5 3
5 4
5 5
4 7
6 8

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

Зато со связью на уровне Entity-классов у нас полный порядок. Начнем с хороших новостей.

Во-первых, у Hibernate есть специальная аннотация @ManyToMany, которая позволяет хорошо описать случай отношения таблиц many-to-many.

Во-вторых, нам по-прежнему достаточно двух Entity-классов. Класс для служебной таблицы нам не нужен.

Вот как будут выглядеть наши классы. Класс Employee в изначальном виде:


@Entity
@Table(name="user")
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;
}

И класс EmployeeTask в его изначальном виде:


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

Аннотация @ManyToMany

Я опущу в примерах существующие поля, зато добавлю новые. Вот как они будут выглядеть. Класс Employee:


@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
	       joinColumns=  @JoinColumn(name="employee_id", referencedColumnName="id"),
           inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )  
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();
 
}

И класс EmployeeTask:


@Entity
@Table(name="task")
class EmployeeTask {
   @Column(name="id")
   public Integer id;
 
   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="task_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )  
   private Set<Employee> employees = new HashSet<Employee>();
 
}

Кажется, что все сложно, но на самом деле там все просто.

Во-первых, там используется аннотация @JoinTable (не путать с @JoinColumn), которая описывает служебную таблицу employee_task.

Во-вторых, там описывается, что колонка task_id таблицы employee_task ссылается на колонку id таблицы task.

В-третьих, там говориться, что колонка employee_id таблицы employee_task ссылается на колонку id таблицы employee.

Мы фактически с помощью аннотаций описали какие данные содержатся в таблице employee_task и как Hibernate должен их интерпретировать.

Зато мы теперь очень просто можем добавить (и удалить) задание любому сотруднику. А также добавить любого исполнителя любому заданию.

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

Давай напишем пару интересных запросов, чтобы лучше понять, как работают эти ManyToMany поля. А работают они абсолютно так, как и ожидается.

Во-первых, наш старый код будет работать без изменений, так как у директора и раньше было поле tasks:


EmployeeTask task1 = new EmployeeTask();
task1.description = "Сделать что-то важное";
session.persist(task1);
 
EmployeeTask task2 = new EmployeeTask();
task2.description = "Ничего не делать";
session.persist(task2);
session.flush();
 
Employee director = session.find(Employee.class, 4);
director.tasks.add(task1);
director.tasks.add(task2);
 
session.update(director);
session.flush();

Во-вторых, если мы захотим назначить какому-то заданию еще одного исполнителя, то сделать это еще проще:


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

Важно! В результате выполнения этого запроса не только у задачи появится исполнитель-директор, но еще и у директора появится задача № 101.

Во-первых, факт о связи директора и задачи в таблице employee_task будет сохранен в виде строки: (4,101).

Во-вторых, поля, помеченные аннотациями @ManyToMany, являются proxy-объектами и при обращении к ним всегда выполняется запрос к базе данных.

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

1
Задача
Модуль 4. Работа с БД, 13 уровень, 3 лекция
Недоступна
Создаём таблицу связи ManyToMany
Представим, что у нас есть две таблицы: - таблица author с колонками id, first_name, last_name, full_name; - таблица book с колонками id, title, publication_year, isbn.
1
Задача
Модуль 4. Работа с БД, 13 уровень, 3 лекция
Недоступна
Соавторство
Есть два класса-энтити: Author и Book. Таблицы, которые им соответствуют: - таблица author с колонками id, first_name, last_name, full_name; - таблица book с колонками id, title, publication_year, isbn.
Комментарии (11)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
12 мая 2025
Кажется, что все сложно, но на самом деле там все очень сложно!
Кирилл Уровень 111 Expert
13 ноября 2023
"Кажется, что все сложно, но на самом деле там все просто" 😁😁
Daniel Уровень 51
8 августа 2023

 красная строка "(NULL) 6" в таблице employee_task. Я отметил ее красным
не отметил
Владимир Уровень 109 Expert
5 января 2023
task1306: Задача открылась с уже выполненной частью задания в main
Anonymous #2470106 Уровень 51
11 января 2023
А как вы задачи смотрите к этому разделу?
Владимир Уровень 109 Expert
11 января 2023
видимо у нас с вами разные подписки, поэтому мне они видны, а вам нет
Дмитрий Уровень 46
1 марта 2023
вы учитесь в джава университете?
Владимир Уровень 109 Expert
1 марта 2023
да
Kirill Krainov-Timanovsky Уровень 41
9 сентября 2023
Простите, здесь и задачи есть?
Эльдар Уровень 108 Expert
8 октября 2023
конэшно =))как без них
Эльдар Уровень 108 Expert
8 октября 2023
Аналогично, красивое готовое решение от Джавараш в комплекте=))