У чому особливості bidirectional relationships
Bidirectional relationships (двонаправлені зв'язки) дозволяють навігацію між сутностями в обох напрямках. Вони вимагають ручної синхронізації обох сторін та правильного використ...
Огляд
Bidirectional relationships (двонаправлені зв’язки) дозволяють навігацію між сутностями в обох напрямках. Вони вимагають ручної синхронізації обох сторін та правильного використання mappedBy.
🟢 Junior Level
Що таке bidirectional relationship
Це зв’язок, де обидві сторони знають одна про одну.
// Order знає про User
@Entity
public class Order {
@ManyToOne
private User user;
}
// User знає про Orders
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
Owner side та Inverse side
Owner side — сторона з FK (Order з @ManyToOne).
Inverse side — сторона з mappedBy (User з @OneToMany(mappedBy = "user")).
🟡 Middle Level
Синхронізація обов’язкова
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Helper methods — обов'язково!
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // ОБОВ'ЯЗКОВО!
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null); // ОБОВ'ЯЗКОВО!
}
}
Чому розсинхронізація — проблема
// ❌ Розсинхронізація
User user = new User();
Order order = new Order();
user.getOrders().add(order);
// order.getUser() == null → FK не встановиться!
// ✅ Синхронізація через helper
user.addOrder(order); // setter + setUser
// order.getUser() == user → FK встановиться
🔴 Senior Level
equals/hashCode для bidirectional
// Використовуйте business key, НЕ ID!
@Entity
public class User {
@Column(unique = true, nullable = false)
private String email; // business key
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return email != null && email.equals(user.email);
}
@Override
public int hashCode() {
return email != null ? email.hashCode() : 0;
}
}
Чому не ID для equals/hashCode
Проблеми з ID:
1. Transient entity (id=null) != Transient entity (id=null)
2. HashSet втрачає entity після persist (id змінюється)
3. Неможливо додати в HashSet до persist
**Якщо немає natural/business key:** використовуйте UUID, що призначається при конструюванні об'єкта.
Best Practices
✅ Helper methods для синхронізації
✅ Business key для equals/hashCode
✅ mappedBy на inverse side
✅ Синхронізація обох сторін
✅ Ініціалізація колекцій
❌ ID для equals/hashCode
❌ Без helper methods
❌ Розсинхронізація
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Bidirectional = навігація в обидві сторони, вимагає синхронізації обох сторін
- Owner side — з FK (@ManyToOne), Inverse side — з mappedBy (@OneToMany)
- Helper methods ОБОВ’ЯЗКОВІ: addOrder/addItem синхронізують обидві сторони
- equals/hashCode по business key (email, SKU), НЕ по ID
- mappedBy тільки на inverse side
Пов’язані теми:
- [[23. Як правильно використовувати @OneToMany і @ManyToOne]]
- [[22. Що таке orphan removal]]
- [[25. Як уникнути нескінченної рекурсії при серіалізації Entity]]
- [[20. Як працюють каскадні операції (Cascade)]]