Как EntityGraph помогает в Hibernate
Что проверяют
Понимаете ли вы, как управлять загрузкой связей под конкретный сценарий, не переводя маппинг сущности на EAGER. Почти всегда следом спрашивают про N+1 и чем EntityGraph отличается от JOIN FETCH.
Как отвечать
EntityGraph это механизм JPA (появился в JPA 2.1), Hibernate его реализует. Граф описывает, какие атрибуты и связи загрузить вместе с корневой сущностью в рамках одного запроса. Способы задать:
@NamedEntityGraph: граф с именем прямо на сущности, переиспользуется в разных запросах;- динамически:
em.createEntityGraph(Author.class), затемaddAttributeNodes("books"), а для вложенных связейaddSubgraph; - в Spring Data:
@EntityGraph(attributePaths = {"books"})на методе репозитория, самый частый вариант в проектах.
Граф передаётся подсказкой, и режимов два. jakarta.persistence.fetchgraph: указанные атрибуты грузятся жадно, остальные по спецификации считаются ленивыми. jakarta.persistence.loadgraph: указанные грузятся жадно, остальные ведут себя так, как написано в маппинге. В Hibernate 6 и Spring Boot 3 пакет уже jakarta.persistence, а не javax.
Против N+1 это работает так: вместо запроса за авторами и отдельного запроса за книгами каждого автора Hibernate строит один SQL с LEFT JOIN. По сути это тот же JOIN FETCH, только вынесенный из текста запроса, поэтому его можно навесить даже на производный метод вроде findByName.
Где спотыкаются
Добавляют в граф две коллекции типа List и получают MultipleBagFetchException. Помогает замена на Set или загрузка второй коллекции отдельным запросом, но декартово произведение строк в SQL при одном запросе никуда не денется.
Совмещают граф по коллекции с пагинацией. Hibernate не может ограничить строки на уровне SQL, поднимает весь результат и режет страницу в памяти, выводя лишь предупреждение. На больших таблицах это кончается нехваткой памяти.
Считают, что fetchgraph гарантированно сделает ленивой связь, помеченную EAGER. Провайдер вправе загрузить больше, чем просили, поэтому надёжнее держать связи ленивыми в самом маппинге.