Hibernate java многие ко многим

Учебное пособие по аннотациям Hibernate «многие ко многим»

В этом кратком руководстве мы кратко рассмотрим, как аннотацию @ManyToMany можно использовать для указания этого типа отношений в Hibernate.

2. Типичный пример

Давайте начнем с простой диаграммы отношений сущностей, которая показывает связь «многие ко многим» между двумя сущностями , сотрудником и проектом:

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

У нас есть таблица сотрудников с employee_id в качестве первичного ключа и таблица проекта с project_id в качестве первичного ключа. Здесь требуется таблица соединений employee_project для соединения обеих сторон.

3. Настройка базы данных

Предположим, у нас есть уже созданная база данных с именем spring_hibernate_many_to_many.

Нам также необходимо создать таблицы employee и project вместе с таблицей соединения employee_project с employee_id и project_id в качестве внешних ключей:

 CREATE TABLE `employee` (   `employee_id` int(11) NOT NULL AUTO_INCREMENT,   `first_name` varchar(50) DEFAULT NULL,   `last_name` varchar(50) DEFAULT NULL,   PRIMARY KEY (`employee_id`)   ) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;    CREATE TABLE `project` (   `project_id` int(11) NOT NULL AUTO_INCREMENT,   `title` varchar(50) DEFAULT NULL,   PRIMARY KEY (`project_id`)   ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;    CREATE TABLE `employee_project` (   `employee_id` int(11) NOT NULL,   `project_id` int(11) NOT NULL,   PRIMARY KEY (`employee_id`,`project_id`),   KEY `project_id` (`project_id`),   CONSTRAINT `employee_project_ibfk_1`   FOREIGN KEY (`employee_id`) REFERENCES `employee` (`employee_id`),   CONSTRAINT `employee_project_ibfk_2`   FOREIGN KEY (`project_id`) REFERENCES `project` (`project_id`)   ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

После настройки базы данных следующим шагом будет подготовка зависимостей Maven и конфигурации Hibernate. Для получения информации об этом, пожалуйста, обратитесь к статье Руководство по Hibernate4 с Spring

4. Классы моделей

Классы модели Employee и Project должны быть созданы с аннотациями JPA:

 @Entity   @Table(name = "Employee")   public class Employee    // .    @ManyToMany(cascade =  CascadeType.ALL >)   @JoinTable(   name = "Employee_Project",   joinColumns =  @JoinColumn(name = "employee_id") >,   inverseJoinColumns =  @JoinColumn(name = "project_id") >   )   SetProject> projects = new HashSet>();    // standard constructor/getters/setters   > 
 @Entity   @Table(name = "Project")   public class Project    // .    @ManyToMany(mappedBy = "projects")   private SetEmployee> employees = new HashSet>();    // standard constructors/getters/setters   > 

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

Чтобы отобразить ассоциацию «многие ко многим», мы используем аннотации @ManyToMany , @JoinTable и @JoinColumn . Давайте посмотрим на них поближе.

Аннотация @ManyToMany используется в обоих классах для создания отношения «многие ко многим» между сущностями.

Эта ассоциация имеет две стороны, т.е. владеющую сторону и обратную сторону. В нашем примере стороной-владельцем является Employee , поэтому таблица соединения указывается на стороне-владельце с помощью аннотации @JoinTable в классе Employee . @JoinTable используется для определения таблицы соединений/связей . В данном случае это Employee_Project.

Аннотация @JoinColumn используется для указания столбца соединения/связывания с основной таблицей. Здесь столбец соединения — employee_id , а project_id — обратный столбец соединения, поскольку Project находится на обратной стороне отношения.

В классе Project атрибут mappedBy используется в аннотации @ManyToMany , чтобы указать, что коллекция сотрудников сопоставляется с коллекцией проектов на стороне владельца.

5. Исполнение

Чтобы увидеть аннотацию «многие ко многим» в действии, мы можем написать следующий тест JUnit:

 public class HibernateManyToManyAnnotationMainIntegrationTest    private static SessionFactory sessionFactory;   private Session session;    //.    @Test   public void givenSession_whenRead_thenReturnsMtoMdata()    prepareData();   @SuppressWarnings("unchecked")   ListEmployee> employeeList = session.createQuery("FROM Employee").list();   @SuppressWarnings("unchecked")   ListProject> projectList = session.createQuery("FROM Project").list();   assertNotNull(employeeList);   assertNotNull(projectList);   assertEquals(2, employeeList.size());   assertEquals(2, projectList.size());    for(Employee employee : employeeList)    assertNotNull(employee.getProjects());   assertEquals(2, employee.getProjects().size());   >   for(Project project : projectList)    assertNotNull(project.getEmployees());   assertEquals(2, project.getEmployees().size());   >   >    private void prepareData()    String[] employeeData =  "Peter Oven", "Allan Norman" >;   String[] projectData =  "IT Project", "Networking Project" >;   SetProject> projects = new HashSetProject>();    for (String proj : projectData)    projects.add(new Project(proj));   >    for (String emp : employeeData)    Employee employee = new Employee(emp.split(" ")[0], emp.split(" ")[1]);   employee.setProjects(projects);    for (Project proj : projects)    proj.getEmployees().add(employee);   >    session.persist(employee);   >   >    //.   > 

Мы можем видеть отношение «многие ко многим» между двумя сущностями, созданными в базе данных: таблицами employee , project и employee_project с образцами данных, представляющими отношения.

6. Заключение

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

Исходный код этого руководства можно найти на GitHub .

Источник

@ManyToMany

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

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

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

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

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

Ну, а таблица 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 ( не изменилась ):

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

А вот так выглядит таблица 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:

Я специально временно сохранил удаленную колонку в таблице 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)

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

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

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

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

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

 @Entity @Table(name="user") class Employee

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

 @Entity @Table(name="task") class EmployeeTask

Аннотация @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 Settasks = new HashSet(); > 
 @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 Setemployees = new HashSet(); > 

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

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

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

Источник

Читайте также:  Javascript function to return text
Оцените статью