Як правильно використовувати @OneToMany і @ManyToOne
@OneToMany та @ManyToOne — найпоширеніші анотації для зв'язків в JPA. Правильне їх використання критично важливе для продуктивності та коректності роботи з базою даних.
Огляд
@OneToMany та @ManyToOne — найпоширеніші анотації для зв’язків в JPA. Правильне їх використання критично важливе для продуктивності та коректності роботи з базою даних.
🟢 Junior Level
Що таке @ManyToOne та @OneToMany
@ManyToOne — багато замовлень до одного користувача.
@OneToMany — один користувач до багатьох замовлень.
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}
Важливі правила
mappedBy— вказує, що зв’язок управляється на іншій стороні@JoinColumn— вказує колонку з foreign key- LAZY — використовуйте для обох сторін
🟡 Middle Level
Bidirectional зв’язок
// Owner side — Order (містить FK column)
@Entity
public class Order {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
// Inverse side — User
@Entity
public class User {
@Id
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Helper method — обов'язково!
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // важливо!
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null); // важливо!
}
}
Чому helper methods обов’язкові
// ❌ Без helper method
User user = new User();
Order order = new Order();
user.getOrders().add(order);
// order.getUser() = null! → FK не встановиться
// ✅ З helper method
User user = new User();
Order order = new Order();
user.addOrder(order); // встановить обидві сторони
// order.getUser() == user ✅
Типові помилки
// ❌ Без ініціалізації колекції
private List<Order> orders; // ❌ NullPointerException при add
private List<Order> orders = new ArrayList<>(); // ✅
// ❌ EAGER для колекції
@OneToMany(fetch = FetchType.EAGER) // ❌ N+1 проблема
private List<Order> orders;
🔴 Senior Level
Внутрішня реалізація
@ManyToOne — FK column в таблиці Order
@OneToMany(mappedBy) — немає FK column, це inverse side
Owner side (@ManyToOne):
- Управляє FK column
- persist(order) → INSERT з user_id
Inverse side (@OneToMany(mappedBy)):
- Не управляє FK
- mappedBy = ім'я поля на owner side
Best Practices
✅ LAZY для обох сторін
✅ Helper methods для синхронізації
✅ mappedBy на inverse side
✅ Ініціалізувати колекції
✅ @JoinColumn на owner side
✅ orphanRemoval для composite children
❌ EAGER для колекцій
❌ Без helper methods
❌ Без ініціалізації колекції
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- @ManyToOne — owner side (містить FK), @OneToMany(mappedBy) — inverse side (немає FK)
- LAZY для обох сторін — золоте правило
- Helper methods обов’язкові для bidirectional: addOrder/removeOrder синхронізують обидві сторони
- mappedBy вказує на поле owner side
- Колекції завжди ініціалізувати: new ArrayList<>()
Пов’язані теми:
- [[24. У чому особливості bidirectional relationships]]
- [[22. Що таке orphan removal]]
- [[2. У чому різниця між Lazy та Eager завантаженням]]
- [[1. Що таке проблема N+1 і як її вирішити]]