Из теории по языку SQL ми знаем что существует такая связь между таблицами как многие ко многим (many to many). Вы знаете что отображать такую связь в реляционных базах данных нужно через вспомогательную таблицу. Теперь остался вопрос – как правильно замапить ManyToMany в Java с помощью Hibernate и аннотаций.
В прошлой статье по хибернейт я показал как замапить поля и классы с помощью аннотаций persistence чтобы отображать таблицы реляционных баз данных как классы джава.
С помощью таких аннотаций мы попытаемся отобразить связь многие ко многим.
Допустим у нас есть таблицы пользователь и роль. Мы предполагаем что пользователь может иметь множество ролей. Также не исключено что одна роль может быть у нескольких пользователей. Это типичная задача для веб разработки. Когда нужно одним пользователям разрешить доступ к ресурсам, а другим нет.
Если посмотреть на такую схему, то приблизительно она будет выглядеть вот так:

Для упрощения схемы я оставил таблицам пользователей и ролей только по два поля.
Как видно на схеме выше – таблица user_role выступает вспомогательной. В ней мы будем хранить ссылку на пользователя и на роль. Причем первичным ключом в ней будет связка двух полей: user_id, role_id.
Попытаемся теперь создать скрипты для базы данных на основе вышеупомянутой таблицы. Я использую PostgreSQL и поэтому скрипт создания таблиц у меня получился такой:
create table users
(
id serial
constraint users_pk
primary key,
login text
);
create unique index users_login_uindex
on users (login);
create table roles
(
id serial
constraint roles_pk
primary key,
name text
);
create table user_roles
(
user_id int
constraint user_roles_users_id_fk
references users,
role_id int
constraint user_roles_roles_id_fk
references roles
);
Для тех, кто знаком с языком SQL понимание примера выше не вызовет трудностей. Если же вы не знаете что здесь происходит – я советую ознакомится с циклом статей SQL.
После того как мы создали таблицы в базе данных, можно приступать к написанию java кода.
Для данного примера я буду использовать Spring Boot фреймворк. В данной статье он будет использован для удобства проверки работоспособности нашего кода.
Первое что я делаю – генерирую Maven проект с необходимыми зависимостями (библиотеками):

Я подключил Spring Data JPA поскольку эта зависимость уже включает в себя Hibernate, Java Persistence API. Плюс ко всему, мне будет удобно продемонстрировать Вам работу маппинга с ее помощью. Больше об этой библиотеке желающие могут почитать в статье Spring Boot — пример с Postgres и JPA
После генерации проекта, я его открыл как Maven проект и добавил настройки к базе данных:

Дальше я создаю классы которые будут отражать мои таблицы пользователей и ролей. Также не забываю навешать на них аннотации JPA:

Мы создали класс Users и класс Roles. Теперь нам нужно указать что у юзеров может быть много ролей. Для этого нам послужит аннотация ManyToMany в связке с JoinTable.
Прежде всего нужно создать в классе Users сет из ролей. Почему сет? Потому что у юзера не может быть 2 записи например из ролью ADMIN. Поэтому здесь лучше использовать сет.
Дальше, нужно навешать аннотацию ManyToMany и после нее JoinTable. В JoinTable нужно указать имя вспомогательной таблицы и поля по которым мы мапим таблицу users и user_roles. Здесь также можно указать имя поля по которому мы связываем user_roles с таблицей roles:

После того как мы добавили inverseJoinColumns, в классе Roles можно теперь просто навешать аннотацию многие ко многим:

В результате всех манипуляций наши классы имеют вид:
package com.example.demo.persistence;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.Set;
@Entity
@Table(name = "users")
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String login;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Roles> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public Set<Roles> getRoles() {
return roles;
}
public void setRoles(Set<Roles> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "Users{" +
"id=" + id +
", login='" + login + '\'' +
", roles=" + roles +
'}';
}
}
package com.example.demo.persistence;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@Entity
@Table(name = "roles")
public class Roles {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@ManyToMany(mappedBy = "roles")
private List<Users> users;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Users> getUsers() {
return users;
}
public void setUsers(List<Users> users) {
this.users = users;
}
@Override
public String toString() {
return "Roles{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Для того, чтобы затестить функционал я создал Spring Boot JPA репозиторий:

В нем будет доступен метод findById которым я и воспользуюсь для демонстрации.
Создадим несколько тестовых ролей и одного тестового юзера:

Добавим в таблицу user_roles данные и пользователе и ролях:

Теперь наш пользователь будет иметь роли ADMIN, USER, MANAGER.
Попробуем их достать. Для этого я в главный класс DemoApplication добавил метод который будет отрабатывать при запуске Spring Boot приложения. В данном методе будем вынимать пользователя и выводить информацию в консоль:

В результате запуска мы видим в консоли что все роли успешно прикреплены за пользователем:

Вот и все что касается связи ManyToMany в Hibernate. Если хотите, можно создать класс который будет отображать дополнительную SQL таблицу и уже через нее связывать юзера и роли. Тогда Вам будет нужна связь один ко многим и проблема многие ко многим будет решена. Я же считаю что если в вспомогательной таблице больше нет никаких интересных для нас данных, то и создавать для нее отдельный класс незачем.