Какие проблемы возникают при использовании жадной загрузки (eager fetch) в Hibernate
Что проверяют
Понимаете ли вы, что FetchType.EAGER задаётся в маппинге и срабатывает при каждой загрузке сущности, нужна связь или нет. Следом почти всегда спрашивают, какие связи жадные по умолчанию и как загрузить данные сразу, не делая связь EAGER.
Как отвечать
Начните с дефолтов JPA: @ManyToOne и @OneToOne жадные, @OneToMany и @ManyToMany ленивые. Поэтому проблемы с жадной загрузкой появляются, даже если никто её явно не включал.
Что ломается:
- N+1 на JPQL:
em.findможет подтянуть связь джойном, но JPQL-запрос или метод Spring Data сначала выбирает сущности, а жадные связи Hibernate догружает отдельными запросами. Список из 100 заказов превращается в 101 запрос; - нельзя отключить:
EAGERиз маппинга не переопределяется на ленивую загрузку в конкретном запросе. Ленивую связь можно загрузить сразу, а от жадной отказаться нельзя; - декартово произведение: две жадные коллекции в одном джойне перемножают строки, а на двух
ListHibernate падает сMultipleBagFetchException; - пагинация: при выборке коллекции джойном Hibernate не может применить
LIMITв SQL, предупреждает и режет страницу в памяти, выгрузив всё. Флагhibernate.query.fail_on_pagination_over_collection_fetchпревращает это в ошибку; - лавина связей: сущность тянет жадную связь, та свою, и чтение одной строки поднимает полбазы.
Правильная схема: все связи LAZY, включая @ManyToOne(fetch = FetchType.LAZY), а нужные данные грузятся под сценарий через JOIN FETCH, @EntityGraph или DTO-проекцию. Для коллекций пригодятся @BatchSize или hibernate.default_batch_fetch_size: они превращают N+1 в несколько запросов с IN. Число запросов проверяйте через hibernate.generate_statistics или счётчик запросов в тестах.
Где спотыкаются
Лечат LazyInitializationException переводом связи в EAGER. Ошибка исчезает, а N+1 расползается по всем местам, где сущность читается.
Не знают, что @ManyToOne жадный по умолчанию, и ищут причину лишних запросов в коде, а не в маппинге.
Путают жадность и способ загрузки: FetchType говорит, когда грузить, а @Fetch(FetchMode.JOIN) или SUBSELECT в Hibernate определяют, каким SQL.