«1. Обзор
JPA 2.1 представила функцию Entity Graph как более сложный метод работы с нагрузкой на производительность.
Это позволяет определить шаблон, сгруппировав связанные поля сохраняемости, которые мы хотим получить, и позволяет нам выбрать тип графика во время выполнения.
В этом уроке мы более подробно объясним, как создать и использовать эту функцию.
2. Что пытается разрешить граф сущностей
До JPA 2.0 для загрузки ассоциации сущностей мы обычно использовали FetchType.LAZY и 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. Определение графа сущностей
Чтобы определить граф сущностей, мы можем либо использовать аннотации к сущности, либо действовать программно, используя JPA API.
5.1. Определение графа сущностей с помощью аннотаций
Аннотация @NamedEntityGraph позволяет указать атрибуты, которые следует включать, когда мы хотим загрузить сущность и связанные с ней ассоциации.
Итак, давайте сначала определим Entity Graph, который загружает сообщение и связанные с ним объекты User и Comment:
@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 для определения связанных объектов, которые будут загружены, когда корень сущность загружена.
Давайте теперь определим более сложный граф сущностей, в который мы также хотим загрузить пользователей, связанных с комментариями.
«Для этой цели мы будем использовать атрибут подграфа @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.baeldung.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. Использование Entity Graph
6.1. Типы графов сущностей
JPA определяет два свойства или подсказки, с помощью которых поставщик постоянства может выбрать для загрузки или извлечения графа сущностей во время выполнения:
-
javax.persistence.fetchgraph — только указанные атрибуты извлекаются из базу данных. Поскольку в этом руководстве мы используем Hibernate, мы можем заметить, что, в отличие от спецификаций JPA, атрибуты, статически настроенные как EAGER, также загружаются. javax.persistence.loadgraph — в дополнение к указанным атрибутам также извлекаются атрибуты, статически сконфигурированные как EAGER.
В любом случае первичный ключ и версия всегда загружаются.
6.2. Загрузка графа сущностей
Мы можем получить граф сущностей различными способами.
Начнем с метода 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.