38. Что такое Entity Graph? Как и для чего его использовать?
UNKNOWNВ JPA 2.1 введен EntityGraph как более сложный способ решения проблем с производительностью при загрузке данных из БД. Он позволяет определить шаблон путем группировки связанных полей, которые мы хотим получить, и позволяет нам выбирать тип графа во время выполнения.
До JPA 2.0 для загрузки связанных сущностей и коллекций мы обычно использовали FetchType.LAZY и FetchType.EAGER в качестве стратегий извлечения.
Это указывало Hibernate, извлекать из БД связанные сущности и коллекции или нет. К сожалению, эта конфигурация статична и не позволяет переключаться между этими двумя стратегиями в рантайме.
Основная цель JPA Entity Graph - улучшить производительность в рантайме при загрузке базовых полей сущности и связанных сущностей и коллекций. Вкратце, Hibernate загружает весь граф в одном SELECT-запросе, то есть все указанные связи от нужной нам сущности:
@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;
//...
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
//...
}
@NamedEntityGraph(
name = \"post-entity-graph-with-comment-users\",
attributeNodes = { 87
@NamedAttributeNode(\"subject\"),
@NamedAttributeNode(\"user\"),
@NamedAttributeNode(value = \"comments\", subgraph = \"comments-subgraph\"),
},
subgraphs = {
@NamedSubgraph(
name = \"comments-subgraph\",
attributeNodes = {
@NamedAttributeNode(\"user\")
}
)
}
)
@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;
//...
}
В данном примере мы создали EntityGraph и при его помощи хотим, чтобы при загрузке сущности Post загружались поля subject, user, comments. Также мы захотели, чтобы у каждой сущности comments загружалось поле user, для чего мы использовали subgraphs.
EntityGraph можно определить и без помощи аннотаций, а используя entityManager из JPA API:
EntityGraph<Post> entityGraph = entityManager.createEntityGraph(Post.class);
entityGraph.addAttributeNodes(\"subject\");
entityGraph.addAttributeNodes(\"user\");
entityGraph.addSubgraph(\"comments\").addAttributeNodes(\"user\");
JPA определяет два свойства или подсказки, с помощью которых Hibernate может выбирать стратегию извлечения графа сущностей во время выполнения:
- fetchgraph - из базы данных извлекаются только указанные в графе атрибуты. Поскольку мы используем Hibernate, мы можем заметить, что в отличие от спецификаций JPA, атрибуты, статически настроенные как EAGER, также загружаются.
- loadgraph - в дополнение к указанным в графе атрибутам, также извлекаются атрибуты, статически настроенные как EAGER. В любом случае, первичный ключ и версия, если таковые имеются, всегда загружаются.
Загрузить EntityGraph можем тремя способами:
- Используя перегруженный метод find(), который принимает Map с настройками EntityGraph:
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);
- Используя JPQL и передав подсказку (hint):
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();
Все они дают следующий результат:
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=?
В каждом из них стратегия графа (fetchgraph, loadgraph) указана как подсказка. В первом примере мы использовали Map, в то время как в двух последующих примерах мы использовали метод setHint().
Предыдущий вопрос: 37. Расскажите про проблему N+1 Select и путях ее решения.
Следующий вопрос: 1. Что такое инверсия контроля (IoC) и внедрение зависимостей (DI)? Какони реализованы в Spring?
Все вопросы по теме: список
Все темы: список
Вопросы/замечания/предложения/нашли ошибку: напишите мне