JPA Entity Graph

JPA Entity Graph

1. обзор

JPA 2.1 представила функцию Entity Graph как более сложный метод борьбы с нагрузкой на производительность.

Он позволяет определить шаблон путем группировки связанных полей персистентности, которые мы хотим получить, и позволяет нам выбирать тип графика во время выполнения.

В этом руководстве мы более подробно объясним, как создать и использовать эту функцию.

2. Что пытается разрешить граф сущностей

До JPA 2.0 для загрузки ассоциации сущностей мы обычно использовали стратегии выборкиFetchType.LAZY иFetchType.EAGER asЭто указывает провайдеру JPA дополнительно получить связанную ассоциацию или нет. Unfortunately, this meta configuration is static и не позволяет переключаться между этими двумя стратегиями во время выполнения.

Основная цель JPA Entity Graph состоит в том, чтобы улучшить производительность во время выполнения при загрузке связанных ассоциаций и основных полей объекта.

Короче говоря, поставщик JPA загружает весь график в одном запросе выбора, а затем избегает извлечения связи с большим количеством запросов SELECT. Это считается хорошим подходом для повышения производительности приложений.

3. Определение модели

Прежде чем мы начнем изучать 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")
    private List comments = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

    //...
}

Пользователь также может комментировать опубликованные сообщения, поэтому, наконец, мы добавим объектComment:

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String reply;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

    //...
}

Как мы видим, сущностьPost связана с сущностямиComment иUser. СущностьComment связана с сущностямиPost иUser.

Цель состоит в том, чтобы загрузить следующий график различными способами:

Post  ->  user:User
      ->  comments:List
            comments[0]:Comment -> user:User
            comments[1]:Comment -> user:User

МетодFetchType определяет две стратегии выборки данных из базы данных:

  • FetchType.EAGER: Провайдер сохраняемости должен загрузить соответствующее аннотированное поле или свойство. Это поведение по умолчанию для саннотированных полей@Basic, @ManyToOne и@OneToOne .

  • FetchType.LAZY: Поставщик сохраняемости должен загружать данные при первом обращении, но может быть загружен быстро. Это поведение по умолчанию для саннотированных полей@OneToMany, @ManyToMany и@ElementCollection-.

Например, когда мы загружаем сущностьPost, связанные сущностиComment не загружаются какFetchType по умолчанию, поскольку@OneToMany - этоLAZY.. Мы можем переопределить это поведение путем измененияFetchType наEAGER:

@OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
private List comments = new ArrayList<>();

Для сравнения: когда мы загружаем объектComment, его родительский объектPost загружается в качестве режима по умолчанию для@ManyToOne, w, который равенEAGER.. Мы также можем не загружатьPost, изменив эту аннотацию наLAZY:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;

Note that as the LAZY is not a requirement, the persistence provider can still load the Post entity eagerly if it wants. Итак, чтобы правильно использовать эту стратегию, мы должны вернуться к официальной документации соответствующего поставщика сохраняемости.

Теперь, поскольку мы использовали аннотации для описания нашей стратегии получения,our definition is static and there is no way to switch between the LAZY and EAGER at runtime.

Именно здесь вступает в игру Entity Graph, как мы увидим в следующем разделе.

5. Определение графа сущностей

Чтобы определить Entity Graph, мы можем либо использовать аннотации к сущности, либо мы можем программно работать с использованием JPA API.

5.1. Определение графа сущностей с аннотациями

Аннотация @NamedEntityGraph позволяет указать атрибуты, которые нужно включить, когда мы хотим загрузить сущность и связанные ассоциации.

Итак, давайте сначала определим Entity Graph, который загружаетPost и связанные с ним объектыUser иComments:

@NamedEntityGraph(
  name = "post-entity-graph",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode("comments"),
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List comments = new ArrayList<>();

    //...
}

В этом примере мы использовали@NamedAttributeNode для определения связанных сущностей, которые должны быть загружены при загрузке корневой сущности.

Давайте теперь определим более сложный Entity Graph, в который мы также хотим загрузитьUsers, связанные сComments.

Для этого воспользуемся атрибутом ssubgraph@NamedAttributeNode . This allows referencing a named subgraph defined through the @NamedSubgraph annotation:

@NamedEntityGraph(
  name = "post-entity-graph-with-comment-users",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"),
  },
  subgraphs = {
    @NamedSubgraph(
      name = "comments-subgraph",
      attributeNodes = {
        @NamedAttributeNode("user")
      }
    )
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List comments = new ArrayList<>();
    //...
}

Определение аннотации@NamedSubgraph аналогично @NamedEntityGraph и позволяет указывать атрибуты связанной ассоциации. Поступая так, мы можем построить полный граф.

В приведенном выше примере с определенным графиком 'post-entity-graph-with-comment-users' мы можем загрузитьPost,, связанныйUser,,Comments иUsers, связанный сComments.с

Наконец, обратите внимание, что в качестве альтернативы мы можем добавить определение Entity Graph, используя дескриптор развертыванияorm.xml:


  
    ...
    
            
    
  
  ...

5.2. Определение графа сущностей с помощью API JPA

Мы также можем определить Entity Graph через APIEntityManager, вызвав методcreateEntityGraph():

EntityGraph entityGraph = entityManager.createEntityGraph(Post.class);

Чтобы указать атрибуты корневой сущности, мы используем методaddAttributeNodes().

entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");

Точно так же, чтобы включить атрибуты из связанной сущности, мы используемaddSubgraph() для построения встроенного Entity Graph, а затем используемaddAttributeNodes() as, как мы делали выше.

entityGraph.addSubgraph("comments")
  .addAttributeNodes("user");

Теперь, когда мы увидели, как создать Entity Graph, в следующем разделе мы рассмотрим, как его использовать.

6. Использование Entity Graph

6.1. Типы графов сущностей

JPA определяет два свойства или подсказки, с помощью которых поставщик сохраняемости может выбирать для загрузки или извлечения графа сущностей во время выполнения:

  • javax.persistence.fetchgraph –  Из базы данных извлекаются только указанные атрибуты. As we are using Hibernate in this tutorial, we can note that in contrast to the JPA specs, attributes statically configured as EAGER are also loaded.

  • javax.persistence.loadgraph –  В дополнение к указанным атрибутам также извлекаются атрибуты, статически настроенные какEAGER.

В любом случае всегда загружаются первичный ключ и версия, если таковая имеется.

6.2. Загрузка графа сущностей

Мы можем получить Entity Graph различными способами.

Let’s start by using the EntityManager.find() method.  Как мы уже показали, режим по умолчанию основан на статических метастратегияхFetchType.EAGER иFetchType.LAZY.

Итак, давайте вызовем методfind() и проверим журнал:

Post post = entityManager.find(Post.class, 1L);

Вот журнал, предоставленный реализацией Hibernate:

select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_
from
    Post post0_
where
    post0_.id=?

Как видно из журнала, сущностиUser иComment не загружаются.

Мы можем переопределить это поведение по умолчанию, вызвав перегруженный методfind(), который принимает подсказки какMap.. Затем мы можемprovide the graph type which we want to load:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);

Если мы снова посмотрим в журнал, мы увидим, что эти объекты теперь загружены и только в одном запросе выбора:

select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_,
    comments1_.post_id as post_id3_0_1_,
    comments1_.id as id1_0_1_,
    comments1_.id as id1_0_2_,
    comments1_.post_id as post_id3_0_2_,
    comments1_.reply as reply2_0_2_,
    comments1_.user_id as user_id4_0_2_,
    user2_.id as id1_2_3_,
    user2_.email as email2_2_3_,
    user2_.name as name3_2_3_
from
    Post post0_
left outer join
    Comment comments1_
        on post0_.id=comments1_.post_id
left outer join
    User user2_
        on post0_.user_id=user2_.id
where
    post0_.id=?

Давайте посмотрим, как добиться того же с помощью JPQL:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
Post post = entityManager.createQuery("select p from Post p where p.id = :id", Post.class)
  .setParameter("id", id)
  .setHint("javax.persistence.fetchgraph", entityGraph)
  .getSingleResult();

И наконец, давайте посмотрим на пример APICriteria:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Post.class);
Root root = criteriaQuery.from(Post.class);
criteriaQuery.where(criteriaBuilder.equal(root.get("id"), id));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();

В каждом из нихthe graph type is given as a hint. Хотя в первом примере мы использовалиMap,, в двух последующих примерах мы использовали методsetHint().

7. Заключение

В этой статье мы рассмотрели использование JPA Entity Graph для динамического полученияEntity и его ассоциаций.

Решение принимается во время выполнения, в котором мы решаем загружать или нет связанную ассоциацию.

Очевидно, что производительность является ключевым фактором, который необходимо учитывать при проектировании объектов JPA. Документация JPA рекомендует использовать стратегиюFetchType.LAZY, когда это возможно, и Entity Graph, когда нам нужно загрузить ассоциацию.

Как обычно доступен весь кодover on GitHub.