Перейти к основному содержимому

График объекта JPA

· 8 мин. чтения

1. Обзор

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

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

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

2. Что пытается решить Entity Graph

До JPA 2.0 для загрузки ассоциации объектов мы обычно использовали FetchType. ЛЕНИВЫЙ и FetchType. EAGER как стратегии извлечения . Это указывает поставщику JPA дополнительно получать связанную ассоциацию или нет. К сожалению, эта мета-конфигурация является статической и не позволяет переключаться между этими двумя стратегиями во время выполнения.

Основная цель 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<Comment> 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<Comment>
comments[0]:Comment -> user:User
comments[1]:Comment -> user:User

4. Загрузка связанных сущностей с помощью стратегий FetchType

Метод 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<Comment> comments = new ArrayList<>();

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

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

Обратите внимание, что, поскольку LAZY не является обязательным требованием, поставщик постоянства может по-прежнему с готовностью загружать объект Post , если он хочет. Поэтому для правильного использования этой стратегии мы должны вернуться к официальной документации соответствующего поставщика постоянства.

Теперь, поскольку мы использовали аннотации для описания нашей стратегии выборки, наше определение статично, и нет возможности переключаться между LAZY и EAGER во время выполнения .

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

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

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

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

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

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

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

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

//...
}

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

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

Для этой цели мы будем использовать атрибут подграфа @NamedAttributeNode . Это позволяет ссылаться на именованный подграф, определенный с помощью аннотации @NamedSubgraph :

@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<Comment> comments = new ArrayList<>();
//...
}

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

В приведенном выше примере с определенным графом « post-entity-graph-with-comment-users» мы можем загрузить сообщение, связанного с ним пользователя , комментарии и пользователей , связанных с комментариями.

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

<entity-mappings>
<entity class="com.foreach.jpa.entitygraph.Post" name="Post">
...
<named-entity-graph name="post-entity-graph">
<named-attribute-node name="comments" />
</named-entity-graph>
</entity>
...
</entity-mappings>

5.2. Определение Entity Graph с помощью JPA API

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

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

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

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

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

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

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

6. Использование диаграммы сущностей

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

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

  • javax.persistence.fetchgraph — из базы данных извлекаются только указанные атрибуты. Поскольку в этом руководстве мы используем Hibernate, мы можем заметить, что, в отличие от спецификаций JPA, атрибуты, статически настроенные как EAGER , также загружаются.
  • javax.persistence.loadgraph — в дополнение к указанным атрибутам также извлекаются атрибуты, статически сконфигурированные как EAGER .

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

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

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

Начнем с использования метода EntityManager.find (). Как мы уже показали, режим по умолчанию основан на статических метастратегиях 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() , который принимает подсказки как карту. Затем мы можем указать тип графика, который мы хотим загрузить:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map<String, Object> 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();

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

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

В каждом из них тип графика дается как подсказка . В то время как в первом примере мы использовали Map, в двух последующих примерах мы использовали метод setHint() .

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

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

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

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

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