Entity Graph — это один из мощных инструментов JPA, который помогает разработчикам гибко управлять загрузкой связанных сущностей. Entity Graph позволяет динамически настраивать загрузку данных во время выполнения программы, что делает его особенно полезным в проектах со сложными структурами данных.
Команда Spring АйО подготовила статью, в которой рассмотрела, как использовать Entity Graph.
В версии JPA 2.1 была добавлена функция Entity Graph, которая улучшает производительность загрузки связанных сущностей. Ранее, в JPA 2.0, для управления стратегиями загрузки данных использовались два подхода: FetchType.LAZY и FetchType.EAGER. Однако эти стратегии статичны, что не позволяет гибко переключаться между ними во время выполнения программы.
Конечно, существует join fetch, который позволяет превратить ленивую загрузку (lazy) в жадную (eager) со стратегией JOIN. Но обратного способа — чтобы из жадной сделать ленивую — средствами JPQL не предусмотрено. И тут на помощь приходит @EntityGraph
! Давайте разберёмся, как с ним работать более подробно…
Прежде чем мы приступим к рассмотрению Entity Graph, определим предметную область, с которой собираемся работать. Допустим, мы хотим создать сайт-блог, где пользователи могут комментировать посты и делиться ими.
Итак, для начала объявим сущность User
:
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; }
Пользователь может делиться различными публикациями, поэтому нам также нужна сущность Post
:
@Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String subject; @OneToMany(mappedBy = "post", fetch = FetchType.LAZY) private List<Comment> comments = new ArrayList<>(); @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") private User user; }
Пользователь также может комментировать опубликованные сообщения, поэтому добавим сущность Comment
:
@Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String reply; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn private Post post; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn private User user; }
Сущность Post
имеет связь с сущностями Comment
и User
. Сущность Comment
имеет связь с сущностями Post
и User
.
Наша задача состоит в том, чтобы загрузить граф:
Post -> user:User -> comments:List<Comment> comments[0]:Comment -> user:User comments[1]:Comment -> user:User
До появления Entity Graph разработчики использовали стратегии FetchType для управления загрузкой связанных данных:
— FetchType.EAGER: Сущность загружается сразу вместе с ее связями (но не всегда именно одним запросом). Подход используется по умолчанию для аннотаций @ManyToOne
и @OneToOne
.
— FetchType.LAZY: Связанные данные загружаются только тогда, когда они запрашиваются. Это поведение по умолчанию для связей @OneToMany
, @ManyToMany
и @ElementCollection
.
Например, при использовании LAZY для сущности Post
, комментарии к посту (сущность Comment
) не будут загружаться по умолчанию:
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY) private List<Comment> comments = new ArrayList<>();
В то же время для связи ManyToOne
поведение по умолчанию — EAGER
, что приводит к автоматической загрузке связанных данных:
@ManyToOne @JoinColumn(name = "user_id") private User user;
Однако, чтобы сделать загрузку более гибкой и управляемой на уровне выполнения, и используется Entity Graph.
Рассмотрим пример с использование @EntityGraph
и Spring Data:
@EntityGraph(attributePaths = {"comments"}) List<Post> findEntityGraphTypeFetchBySubject(String subject);
Граф будет включать в себя загрузку всех базовых полей пользователя, который создал пост (user), а также комментарии и их авторов.
Hibernate: select p1_0.id, c1_0.post_id, c1_0.id, c1_0.reply, c1_0.user_id, p1_0.subject, p1_0.user_id from post p1_0 left join comment c1_0 on p1_0.id=c1_0.post_id where p1_0.subject=?
Стоит отметить, что у самого EntityGraph также есть 2 вида загрузки: EntityGraph.EntityGraphType.FETCH
и EntityGraph.EntityGraphType.LOAD
. При выборе режима Fetch (используется по умолчанию) ассоциативные атрибуты, явно объявленные для загрузки, например comments в нашем случае, будут выгружены жадно (FetchType.EAGER), остальные же атрибуты будут загружены лениво (FetchType.LAZY).
В случае же если мы будем использовать режим EntityGraph.EntityGraphType.LOAD
, выбранные атрибуты будут загружены жадно, а остальные атрибуты будут загружены в соответствии с тем, какой FetchType указан в модели.
@EntityGraph(attributePaths = {"comments"}, type = EntityGraph.EntityGraphType.LOAD) List<Post> findEntityGraphTypeLoadBySubject(String subject);
Hibernate: select p1_0.id, c1_0.post_id, c1_0.id, c1_0.reply, c1_0.user_id, p1_0.subject, p1_0.user_id from post p1_0 left join comment c1_0 on p1_0.id=c1_0.post_id where p1_0.subject=? Hibernate: select u1_0.id, u1_0.email, u1_0.name from user_ u1_0 where u1_0.id=? Hibernate: select u1_0.id, u1_0.email, u1_0.name from user_ u1_0 where u1_0.id=? //И так далее, N+1 запрос
Как можно заметить, вся информация о пользователях грузится в соответствии с тем, как указано в модели – то есть жадно, хоть мы и не указывали поле user для @EntityGraph.
Отдельно отметим, базовые атрибуты будут загружены всегда, независимо от выбранного режима загрузки, будь то FETCH или LOAD. Поэтому указывать их в качестве значений для attributePaths нет смысла.
Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
Ждем всех, присоединяйтесь
ссылка на оригинал статьи https://habr.com/ru/articles/844336/
Добавить комментарий