В чем негативная сторона решения проблемы N+1 с помощью join?
Что проверяют
Знаете ли вы, что JOIN FETCH не бесплатная замена ленивой загрузки, и видите ли, где он ломается в Hibernate. Следом почти всегда спрашивают про пагинацию и про альтернативы: batch fetching, отдельный запрос по идентификаторам.
Как отвечать
Начните с механики. N+1 возникает, когда вы загрузили N родителей, а затем ленивая коллекция каждого делает свой запрос. JOIN FETCH или @EntityGraph забирают всё одним запросом, но у этого есть цена:
- размножение строк: у 100 заказов по 50 позиций результат уже 5000 строк, и каждая строка заново тащит все колонки заказа. Растут трафик от базы и работа по сборке объектов;
- две коллекции сразу:
JOIN FETCHпо двумListу одной сущности даётMultipleBagFetchException. Замена наSetубирает исключение, но умножает строки: 50 позиций на 20 платежей это уже 1000 строк на один заказ; - пагинация:
setMaxResultsвместе с fetch коллекции нельзя применить в SQL, поэтому Hibernate пишет предупреждениеapplying in memory, загружает всё и режет страницу в памяти. Настройкойhibernate.query.fail_on_pagination_over_collection_fetchэто превращают в ошибку; - жадность везде: граф загрузки, придуманный для одного экрана, начинают переиспользовать там, где коллекция не нужна.
Затем назовите альтернативы. @BatchSize или hibernate.default_batch_fetch_size превращают N запросов в несколько запросов с IN. @Fetch(FetchMode.SUBSELECT) догружает коллекцию одним подзапросом. Для страниц работает схема из двух запросов: сначала идентификаторы нужной страницы, затем сущности по ним с JOIN FETCH. Для чтения без изменений часто проще DTO-проекция, собранная одним запросом.
Где спотыкаются
Не знают про пагинацию в памяти. На тестовых данных всё быстро, а на проде страница из 20 записей поднимает в память всю выборку.
Лечат MultipleBagFetchException переходом на Set и считают задачу закрытой, хотя получили ещё большее декартово произведение.
Называют минусом «сложный SQL» или «JOIN медленный сам по себе». Проблема не в соединении, а в размножении строк и в том, что Hibernate потом собирает из них объекты.