Ответ вслух Middle Hibernate: N+1, lazy, кеш спрашивают часто обновлено

N+1: найти и исправить

Вопрос

Расскажите, что такое проблема N+1 в Hibernate, как вы её находите и как исправляете.

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

N+1 это когда загрузили список одним запросом, а потом для каждого элемента пошёл отдельный запрос за связью. Чаще всего причина в обходе ленивых коллекций в цикле, в EAGER @ManyToOne при JPQL-запросах и в сериализации сущностей в JSON при включённом OSIV.

Что должно прозвучать в ответе

  • Определение: 1 запрос на список и N на связи

  • Причины: ленивые коллекции в цикле, EAGER ManyToOne, сериализация сущностей

  • Поиск: логи SQL, статистика Hibernate, тест на число запросов, APM

  • join fetch и EntityGraph

  • BatchSize или default_batch_fetch_size

  • Ограничения fetch: пагинация, MultipleBagFetchException; DTO-проекции

Что могут спросить следом

  • Почему EAGER не решает проблему?
  • Как совместить fetch коллекций и пагинацию?

Разбор

N+1 это когда загрузили список одним запросом, а потом для каждого элемента пошёл отдельный запрос за связью. Чаще всего причина в обходе ленивых коллекций в цикле, в EAGER @ManyToOne при JPQL-запросах и в сериализации сущностей в JSON при включённом OSIV. Нахожу по логам SQL в тестах, через hibernate.generate_statistics, в проде по APM, где видно сотни одинаковых запросов на один HTTP-вызов. Исправляю загрузкой нужного заранее: join fetch или @EntityGraph на методе репозитория. Если коллекций несколько или нужна пагинация, fetch не подходит: тогда сначала беру страницу id, потом догружаю связи одним запросом по IN, или включаю default_batch_fetch_size. Когда нужны только поля, беру DTO-проекцию. EAGER не лечит N+1, он лишь делает его постоянным.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.