Java Academy · Урок

Связи один-ко-многим и многие-ко-многим

Настраивайте @OneToMany, @ManyToMany и @JoinTable с типами каскадирования и стратегиями загрузки.

Урок 3 из 413 шагов

«Связи один-ко-многим и многие-ко-многим» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Типы связей в JPA

JPA поддерживает четыре типа связей: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Каждый из них сопоставляется с ограничениями внешних ключей или таблиц соединений.

@ManyToOne: Сторона-владелец

Связь «многие к одному» (например, многие Order принадлежат одному User) задаётся с помощью @ManyToOne и @JoinColumn для внешнего ключа.

@Entity
public class Order {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

@OneToMany: Обратная сторона

На обратной стороне (у User много Order) используется @OneToMany(mappedBy = "user"). Атрибут mappedBy указывает на поле стороны-владельца, в котором хранится FK.

@Entity
public class User {
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();
}

Вспомогательный метод для согласованности двунаправленной связи

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

public void addOrder(Order order) {
    orders.add(order);
    order.setUser(this); // keep both sides in sync
}
public void removeOrder(Order order) {
    orders.remove(order);
    order.setUser(null);
}

Типы каскадирования

CascadeType.ALL распространяет все операции (сохранение, объединение, удаление, обновление и отсоединение) от родительской сущности к дочерней. PERSIST и MERGE — распространённые безопасные варианты. REMOVE удаляет дочерние сущности при удалении родительской.

@OneToMany(mappedBy = "user",
           cascade = {CascadeType.PERSIST, CascadeType.MERGE},
           orphanRemoval = true)
private List<Order> orders;

orphanRemoval: автоматическое удаление дочерних сущностей

orphanRemoval = true удаляет дочернюю сущность, когда её удаляют из коллекции родительской сущности, — даже без явного вызова entityManager.remove().

user.getOrders().remove(order); // triggers DELETE for the orphaned order on flush

@ManyToMany с @JoinTable

Связь «многие ко многим» (например, между Student и Course) использует таблицу связей. Определите её на стороне-владельце с помощью @JoinTable; на обратной стороне используйте mappedBy.

@Entity
public class Student {
    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id"))
    private Set<Course> courses = new HashSet<>();
}

Дополнительные столбцы в таблице связей

Если таблице связей нужны дополнительные столбцы (например, дата зачисления), замените @ManyToMany двумя связями @OneToMany / @ManyToOne и создайте новую сущность, представляющую таблицу связей.

@Entity
public class Enrollment {
    @ManyToOne Student student;
    @ManyToOne Course course;
    private LocalDate enrolledAt;
}

FetchType: LAZY против EAGER

Загрузка по умолчанию: @ManyToOne и @OneToOne используют EAGER (загрузка выполняется сразу). @OneToMany и @ManyToMany используют LAZY (загрузка выполняется при обращении). Для коллекций всегда используйте LAZY, чтобы избежать запросов N+1.

// Always override to LAZY:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;

Проблема N+1 Query

Обращение к ленивой коллекции внутри цикла запускает по одному запросу для каждой сущности (N+1). Используйте JOIN FETCH в JPQL или @EntityGraph, чтобы загрузить коллекцию одним запросом.

// N+1 problem:
List<User> users = repo.findAll();
users.forEach(u -> u.getOrders().size()); // 1 + N queries!
// Fix:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();

Set и List для коллекций

Используйте Set для @ManyToMany (это предотвращает появление повторяющихся строк в таблице связей). Используйте List для @OneToMany, если важен порядок. Никогда не используйте List с @ManyToMany — Hibernate удалит и заново вставит все строки.

Быстрая проверка

Что означает mappedBy в аннотации @OneToMany?

Итоги

Используйте @ManyToOne+@JoinColumn на стороне-владельце (FK). На обратной стороне используйте @OneToMany(mappedBy=...). Для коллекций всегда выбирайте LAZY. Исправляйте проблему N+1 с помощью JOIN FETCH. Для связи «многие ко многим» с дополнительными столбцами используйте отдельную сущность связи.

Можно начать бесплатно

Изучай Java с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
104
Уроки
374

Часто задаваемые вопросы

Урок «Связи один-ко-многим и многие-ко-многим» бесплатный?

Да — полный текст урока «Связи один-ко-многим и многие-ко-многим» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Связи один-ко-многим и многие-ко-многим»?

Настраивайте @OneToMany, @ManyToMany и @JoinTable с типами каскадирования и стратегиями загрузки. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Связи один-ко-многим и многие-ко-многим»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Отображение сущностей с аннотациями JPA
  2. Репозитории Spring Data и методы запросов
  3. Связи один-ко-многим и многие-ко-многим
  4. Пагинация, сортировка и проекции
← Назад к Java Academy