Питання 23 · Розділ 16

Як правильно використовувати @OneToMany і @ManyToOne

@OneToMany та @ManyToOne — найпоширеніші анотації для зв'язків в JPA. Правильне їх використання критично важливе для продуктивності та коректності роботи з базою даних.

Мовні версії: English Russian Ukrainian

Огляд

@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;
}

Важливі правила

  1. mappedBy — вказує, що зв’язок управляється на іншій стороні
  2. @JoinColumn — вказує колонку з foreign key
  3. 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 і як її вирішити]]