JPA Entity Graph и нюансы его использования

от автора

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *