Связь ManyToMany в Hibernate

Связь ManyToMany в Hibernate

Из теории по языку 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 Boot проекта с зависимостями spring data jpa и PostgreSQL driver

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

После генерации проекта, я его открыл как Maven проект и добавил настройки к базе данных:

Структура Spring Boot проекта

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

базовая настройка классов отображения сущностей в базе данных

Мы создали класс Users и класс Roles. Теперь нам нужно указать что у юзеров может быть много ролей. Для этого нам послужит аннотация ManyToMany в связке с JoinTable.

Прежде всего нужно создать в классе Users сет из ролей. Почему сет? Потому что у юзера не может быть 2 записи например из ролью ADMIN. Поэтому здесь лучше использовать сет.

Дальше, нужно навешать аннотацию ManyToMany и после нее JoinTable. В JoinTable нужно указать имя вспомогательной таблицы и поля по которым мы мапим таблицу users и user_roles. Здесь также можно указать имя поля по которому мы связываем user_roles с таблицей roles:

пометка поля класса аннотацией many to many.

После того как мы добавили 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 репозиторий:

пустой Spring Boot JPA репозиторий

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

Создадим несколько тестовых ролей и одного тестового юзера:

создание тестовых ролей и тестового юзера

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

Теперь наш пользователь будет иметь роли ADMIN, USER, MANAGER.

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

демонстрация запуска Spring Boot приложения

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

результат работы программы

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *