Gymterview
middle

Маппинг связей между сущностями

Hibernate поддерживает четыре типа связей, соответствующих отношениям в реляционной модели.

@ManyToOne / @OneToMany — самая частая связь

Пример двунаправленной связи
// Сторона "многие" — владелец связи (содержит FK)
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

// Сторона "один" — обратная сторона
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    public void removeOrder(Order order) {
        orders.remove(order);
        order.setUser(null);
    }
}

@OneToOne

Пример
@Entity
public class User {
    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
              fetch = FetchType.LAZY, optional = false)
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id
    private Long id; // общий PK с User

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId // PK UserProfile = FK на User
    @JoinColumn(name = "id")
    private User user;
}

@ManyToMany

Пример
@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<>();
}

@Entity
public class Course {
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

Важное

  • mappedBy — указывает обратную (не владеющую) сторону связи; FK хранится на стороне без mappedBy
  • Всегда используйте FetchType.LAZY для @OneToMany и @ManyToMany
  • @ManyToOne по умолчанию EAGER — явно указывайте LAZY
  • Для @ManyToMany предпочитайте Set вместо List — производительнее при операциях удаления
  • Синхронизируйте обе стороны связи (helper-методы addOrder, removeOrder)

Частые ошибки

  • Не указывать mappedBy — Hibernate создаст промежуточную таблицу вместо использования FK
  • Использовать List в @ManyToMany — при удалении Hibernate удалит все записи из join-таблицы и вставит заново; Set удаляет точечно
  • EAGER для коллекций — загрузка всех связанных сущностей сразу ведёт к N+1 и OutOfMemoryError
  • Двунаправленная связь без синхронизации — если не обновить обе стороны, in-memory модель не совпадает с БД
  • @OneToOne с LAZY — LAZY для @OneToOne на стороне без FK не работает без bytecode enhancement (Hibernate должен знать, null ли связь, что требует запроса)

Как используется в 2026

  • @ManyToOne(fetch = LAZY) + JOIN FETCH — золотой стандарт
  • @ManyToMany часто заменяется промежуточной сущностью (@Entity StudentCourse) для добавления атрибутов связи
  • Hibernate 6 улучшил работу с @OneToOne LAZY через bytecode enhancement

На собеседовании: начните с @ManyToOne/@OneToMany как самой частой связи. Объясните mappedBy — кто владелец связи и где FK. Ключевое правило — всегда LAZY. Интервьюер может спросить про подводные камни @ManyToMany — ответ: используйте Set, а лучше промежуточную сущность.