Какие стратегии загрузки (FetchType) используются в Hibernate по умолчанию?

Ответ

Hibernate использует разные стратегии загрузки по умолчанию для разных типов ассоциаций, чтобы сбалансировать производительность и удобство.

Правила по умолчанию:

  1. FetchType.EAGER (жадная загрузка): Для @ManyToOne и @OneToOne.
  2. FetchType.LAZY (ленивая загрузка): Для @OneToMany и @ManyToMany.

Пример сущности:

@Entity
public class Order {
    @Id
    private Long id;

    // Загружается сразу вместе с Order (EAGER)
    @ManyToOne
    private Customer customer;

    // Загружается только при обращении к полю (LAZY)
    @OneToMany(mappedBy = "order")
    private List<Item> items = new ArrayList<>();
}

Почему такие настройки?

  • EAGER для @ManyToOne: Обычно «многие» заказы ссылаются на одного клиента. Загрузка клиента сразу часто нужна и эффективна.
  • LAZY для @OneToMany: У одного заказа может быть много позиций. Их жадная загрузка может привести к N+1 проблеме или избыточному объему данных. Ленивая загрузка позволяет загружать коллекцию только когда она действительно требуется.

Важно: Рекомендуется явно указывать FetchType.LAZY для всех ассоциаций, кроме критически необходимых, и использовать JOIN FETCH в запросах для контролируемой загрузки.

Ответ 18+ 🔞

А, слушай, вот эта тема с Hibernate и его стратегиями загрузки — это просто ёперный театр, если честно. Ну, типа, он там сам по себе, этот хибер, решает, что ему грузить сразу, а что — потом, когда спросят. И у него там свои, блядь, правила по умолчанию, которые вроде как для нашего же удобства, но иногда от них волосы дыбом, ебать мои старые костыли.

Вот смотри, какие у него приколы:

Как он по дефолту работает, этот хитрый жоп:

  1. FetchType.EAGER (жадная загрузка): Включается автоматом для связей @ManyToOne и @OneToOne. То есть, грузит всё сразу, не стесняясь.
  2. FetchType.LAZY (ленивая загрузка): Сам включается для @OneToMany и @ManyToMany. То есть, будет тянуть данные только когда ты к ним полезешь, хитрая жопа.

Вот тебе пример, чтобы не быть голословным:

@Entity
public class Order {
    @Id
    private Long id;

    // Этот клиент загрузится сразу, вместе с заказом (EAGER)
    @ManyToOne
    private Customer customer;

    // А вот эта куча позиций в заказе — нет. Загрузится только если спросишь (LAZY)
    @OneToMany(mappedBy = "order")
    private List<Item> items = new ArrayList<>();
}

А теперь, блядь, почему так, а не иначе?

  • EAGER для @ManyToOne: Ну, логично же, вроде. «Много» заказов обычно ссылаются на «одного» клиента. И почти всегда, когда ты заказ смотришь, тебе и клиента его надо. Вот хибер и думает: «А зачем два раза ходить? Сразу всё принесу». Вроде бы эффективно, да.
  • LAZY для @OneToMany: А вот тут уже, блядь, подстава может быть. У одного заказа позиций может быть овердохуища. Представь, что ты грузишь список из 100 заказов, а у каждого по 50 позиций. Если всё это потянуть жадно, получишь 5000 записей разом, и сервер тебе скажет «иди нахуй» красиво и без мата. Поэтому умный Hibernate делает вид, что коллекции нет, пока ты её не тронешь. Лениво, зато безопасно.

Но вот что я тебе скажу, как человек, который уже на этих граблях плясал: лучшая практика — это явно везде, где только можно, ставить FetchType.LAZY. Да-да, даже на тех @ManyToOne, где он по умолчанию жадный. Потому что потом, когда тебе реально понадобится эта связь в конкретном запросе, ты её явно подтянешь через JOIN FETCH в JPQL. Так ты контролируешь, что и когда грузится, а не надеешься на авось и дефолтные настройки этой, с позволения сказать, мартышлюшки. Иначе однажды проснёшься с проблемой N+1 запросов, и будешь думать: «Ну я же, блядь, мудак... что я натворил?».