Связи один-ко-многим и многие-ко-многим
Настраивайте @OneToMany, @ManyToMany и @JoinTable с типами каскадирования и стратегиями загрузки.
«Связи один-ко-многим и многие-ко-многим» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Отображение сущностей с аннотациями JPA
- Репозитории Spring Data и методы запросов
- Связи один-ко-многим и многие-ко-многим
- Пагинация, сортировка и проекции