← Назад к вопросам

Что такое отношение Many-to-Many в Hibernate?

2.0 Middle🔥 231 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое отношение Many-to-Many в Hibernate?

Отношение Many-to-Many — это связь между двумя сущностями, где одна запись первой таблицы может быть связана с несколькими записями второй таблицы, и наоборот. В базе данных такое отношение обычно реализуется через промежуточную таблицу (junction table).

Физическая структура

Для отношения Many-to-Many требуется третья таблица для связи:

Student                StudentCourse              Course
+----------+           +----------+----------+   +----------+
| id (PK)  |---------->| student_id (FK)        <--| id (PK)  |
| name     |           | course_id (FK)  |------>| name     |
+----------+           +----------+----------+   +----------+

Аннотация @ManyToMany

В Hibernate отношение Many-to-Many определяется аннотацией @ManyToMany:

@Entity
@Table(name = "students")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
    
    // getters, setters
}

@Entity
@Table(name = "courses")
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
    
    // getters, setters
}

Аннотация @JoinTable

@JoinTable определяет промежуточную таблицу и её столбцы:

@JoinTable(
    name = "student_course",
    joinColumns = @JoinColumn(name = "student_id"),
    inverseJoinColumns = @JoinColumn(name = "course_id")
)

Owning Side и Inverse Side

В Many-to-Many отношении одна сторона называется owning side (владельцем), другая — inverse side (обратной стороной):

  • Owning side — сторона, которая определяет @JoinTable
  • Inverse side — сторона с параметром mappedBy
// Student — owning side (определяет таблицу связи)
@ManyToMany
@JoinTable(...)
private Set<Course> courses;

// Course — inverse side (читает из того же отношения)
@ManyToMany(mappedBy = "courses")
private Set<Student> students;

Hibernate синхронизирует отношение через owning side, поэтому при сохранении важно добавлять элементы именно в эту коллекцию.

Использование в коде

Student student = entityManager.find(Student.class, 1L);
Set<Course> courses = student.getCourses();

Course javaCourse = entityManager.find(Course.class, 1L);
student.getCourses().add(javaCourse);

entityManager.merge(student);

student.getCourses().remove(javaCourse);

Каскадные операции

Можно управлять поведением при удалении через cascade:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(...)
private Set<Course> courses;
  • PERSIST — сохранение связанного объекта при сохранении
  • MERGE — слияние при обновлении
  • REMOVE — удаление связанного объекта при удалении

Загрузка данных

По умолчанию коллекции загружаются лениво (FetchType.LAZY):

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(...)
private Set<Course> courses;

N+1 проблема

// ПЛОХО: выполнит 1 запрос за студентов + N запросов за курсы
List<Student> students = entityManager.createQuery(
    "SELECT s FROM Student s", Student.class
).getResultList();

for (Student s : students) {
    System.out.println(s.getCourses());
}

// ХОРОШО: один запрос со FETCH JOIN
List<Student> students = entityManager.createQuery(
    "SELECT DISTINCT s FROM Student s JOIN FETCH s.courses", Student.class
).getResultList();

Many-to-Many отношение — мощный инструмент Hibernate для моделирования сложных связей между сущностями, но требует понимания owning/inverse sides и проблем с производительностью.