Существуют две стратегии загрузки полей сущности: FetchType.EAGER и FetchType.LAZY. С помощью Entity Graph можно менять стратегию во время выполнения программы.
Модель
Продолжим эксперименты с нашей моделью (пост с тегами и картинками), только теперь над сущностью Post зададим @NamedEntityGraph:
@NamedEntityGraphs({
@NamedEntityGraph(
name = "post-entity-graph",
attributeNodes = {
@NamedAttributeNode(value = "images")
}
)
})
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String title;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Image> images = new ArrayList<>();
@ElementCollection
private Set<String> tags = new HashSet<>();
public Post(String title) {
this.title = title;
}
public void addImage(Image image) {
image.setPost(this);
this.images.add(image);
}
// getters/setters/constructor/toString
}
Картинка Image:
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String url;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
//getters/setters/constructors/toString
}
@NamedEntityGraph
Каждая аннотация @NamedEntityGraph (а их может быть несколько) задает свою стратегию загрузки полей. Те поля, которые перечислены в attributeNodes получают fetch = FetchType.EAGER.
Как работает загрузка по умолчанию без Entity Graph
Известно, что по умолчанию коллекции загружаются лениво (FetchType.LAZY), то есть при выборке постов:
@Test
@DisplayName("если не использовать EntityGraph, коллекции не загружаются")
public void givenDefaultFetchStrategy_whenFind_thenCollectionsAreLazy() {
HibernateUtil.doInHibernate(session -> {
Post post = session.find(Post.class, 1l);
});
}
В консоли мы увидим select только из таблицы post (а tags и images не загружаются):
select post0_.id as id1_1_0_, post0_.title as title2_1_0_ from Post post0_ where post0_.id=?
Теперь с Entity Graph
Если же использовать Entity Graph, то загрузятся еще и коллекции, которые упомянуты в графе в качестве атрибута.
Статический Entity Graph
Сначала используем граф post-entity-graph, который задали в аннотации.
@Test
@DisplayName("если использовать EntityGraph, загружаются images")
public void givenEntityGraph_whenFind_thenImagesAreEager() {
HibernateUtil.doInHibernate(session -> {
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", session.getEntityGraph("post-entity-graph"));
Post post = session.find(Post.class, 1l, properties);
});
}
В нем указано, что нужно загружать images, что и происходит:
select post0_.id as id1_1_0_, post0_.title as title2_1_0_, images1_.post_id as post_id3_0_1_,
images1_.id as id1_0_1_, images1_.id as id1_0_2_, images1_.post_id as post_id3_0_2_,
images1_.url as url2_0_2_
from Post post0_ left outer join Image images1_
on post0_.id=images1_.post_id where post0_.id=?
Динамический Entity Graph
Но не обязательно задавать граф в аннотации, можно сформировать его на ходу. Создадим граф, загружающий теги, и выполним find():
@Test
@DisplayName("если создать динамически EntityGraph, загружаются tags")
public void givenDynamicEntityGraph_whenFind_thenTagsAreEager() {
HibernateUtil.doInHibernate(session -> {
Map<String, Object> properties = new HashMap<>();
EntityGraph<Post> postGraph = session.createEntityGraph(Post.class);
postGraph.addAttributeNodes("tags");
properties.put("javax.persistence.fetchgraph", postGraph);
Post post = session.find(Post.class, 1l, properties);
});
}
Выше мы задали граф с атрибутом tags.
В консоли видно, что делается выборка не только из таблицы постов Post, но и тегов Post_tags:
select post0_.id as id1_1_0_, post0_.title as title2_1_0_,
tags1_.Post_id as Post_id1_2_1_, tags1_.tags as tags2_2_1_
from Post post0_ left outer join Post_tags tags1_
on post0_.id=tags1_.Post_id where post0_.id=?
До сих пор мы выполняли запросы методом find(). Теперь сделаем то же самое с помощью createQuery():
Как указать Entity Graph в Query
@Test
@DisplayName("если использовать EntityGraph, загружаются images")
public void givenEntityGraph_whenQuery_thenImagesAreEager() {
HibernateUtil.doInHibernate(session -> {
EntityGraph entityGraph = session.getEntityGraph("post-entity-graph");
Post post = session.createQuery("select p from Post p where p.id = :id", Post.class)
.setParameter("id", 1l)
.setHint("javax.persistence.fetchgraph", entityGraph)
.getSingleResult();
});
}
Как видите, граф мы указали в методе setHint().
Типы Entity Graph: fetch vs load
Стоит упомянуть еще о типах графа. До сих пор мы использовали Entity Graph типа fetch. Его конфигурация полностью переопределяет fetching strategy по умолчанию. Это значит, что если бы, например, в описании класса теги были аннотированы с помощью FetchType.EAGER:
@ElementCollection(fetch = FetchType.EAGER) private Set<String> tags = new HashSet<>();
то они все равно бы не загружались, если не упомянуты в графе.
Entity Graph же типа load дополняет стратегию по умолчанию. То есть не упомянутые в графе теги загружались бы все равно из-за FetchType.EAGER в описании класса.
Тип графа мы задавали в строках:
properties.put("javax.persistence.fetchgraph", postGraph);
.setHint("javax.persistence.fetchgraph", entityGraph)
Для графа типа load было бы так:
javax.persistence.loadgraph
Протестируем loadgraph. Для этого пометим коллекцию тегов (fetch = FetchType.EAGER):
@Data
@NoArgsConstructor
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String title;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<Image> images = new ArrayList<>();
@ElementCollection(fetch = FetchType.EAGER)
private Set<String> tags = new HashSet<>();
public Post(String title) {
this.title = title;
}
public void addImage(Image image) {
image.setPost(this);
this.images.add(image);
}
...
}
А затем извлечем посты с loadgraph, при этом в граф не добавляем никаких атрибутов:
@Test
@DisplayName("если создать loadgraph и сделать теги (fetch = FetchType.EAGER), то загружаются tags")
public void givenLoadGraph_whenFind_thenTagsAreEager() {
HibernateUtil.doInHibernate(session -> {
Map<String, Object> properties = new HashMap<>();
EntityGraph<Post> postGraph = session.createEntityGraph(Post.class);
properties.put("javax.persistence.loadgraph", postGraph);
Post post = session.find(Post.class, 1l, properties);
});
}
Несмотря на то, что атрибуты не добавлены в граф, они извлекаются:
select post0_.id as id1_1_0_, post0_.title as title2_1_0_, tags1_.Post_id as post_id1_2_1_, tags1_.tags as tags2_2_1_ from Post post0_ left outer join Post_tags tags1_ on post0_.id=tags1_.Post_id where post0_.id=?
Если добавить с тест строку, то загрузятся и теги, и images:
postGraph.addAttributeNodes("images");
SQL будет такой:
select post0_.id as id1_1_0_, post0_.title as title2_1_0_, images1_.post_id as post_id3_0_1_, images1_.id as id1_0_1_, images1_.id as id1_0_2_, images1_.post_id as post_id3_0_2_, images1_.url as url2_0_2_, tags2_.Post_id as post_id1_2_3_, tags2_.tags as tags2_2_3_ from Post post0_ left outer join Image images1_ on post0_.id=images1_.post_id left outer join Post_tags tags2_ on post0_.id=tags2_.Post_id where post0_.id=?
Подграфы
Entity Graph может быть сложным. Если загружаемая сущность Image в свою очередь тоже ссылается на какую-нибудь коллекцию colors или другую сущность, то для нее тоже можно задать вложенный Entity Graph:
@NamedEntityGraphs({
@NamedEntityGraph(
name = "post-entity-graph",
attributeNodes = {
@NamedAttributeNode(value = "images", subgraph = "colors-subgraph")
},
subgraphs = {
@NamedSubgraph(
name = "colors-subgraph",
attributeNodes = {
@NamedAttributeNode("colors")
}
)
}
)
})
Итоги
Код примера есть на GitHub.
Еще об Entity Graph в Spring Data.
Относительно раздела 4 статьи.
Здесь [https://www.baeldung.com/jpa-entity-graph] в разделе 6.1 сказано, что помеченные как EAGER по атрибуты все же загружаются.
да, все верно, у меня также сказано: если пометить в Post коллекцию тегов как (fetch = FetchType.EAGER) и не упоминать теги в loadgraph, то они все равно загрузятся. Потому что loadgraph дополняет, а не перезаписывает FetchType.EAGER-атрибуты. Все EAGER, которые были в посте, остаются в loadgraph.
Пример с loadgraph добавлен.
Вот цитата из статьи где говорится о fetchgraph:
«…если бы, например, в описании класса теги были аннотированы с помощью FetchType.EAGER … то они все равно бы НЕ загружались, если не упомянуты в графе».
Но, насколько я понял из 6.1 [https://www.baeldung.com/jpa-entity-graph] (выделенное жирным шрифтом место), в Hibernate, при установленном режиме fetchgraph, атрибуты помеченные EAGER будут загружаться, даже если они не перечислены в EntityGraph-е.
А, про fetchgraph речь.
Да, действительно, у них сказано, что Hibernate (несмотря на спецификацию JPA) все равно загружает «attributes statically configured as EAGER» (поля с fetch = FetchType.EAGER, если правильно понимаю).
Но, насколько я тестирую с hibernate 5.5.7.Final, аннотация FetchType.EAGER не дает загрузки, если в fetchgraph поле не упомянуто. Только что проверено и для поля OneToMany в Post, и для ManyToOne в Image. И для ElementCollection.
Вот как. Интересное наблюдение. Наверное тогда loadgraph безопаснее использовать (по крайней мере в том кейсе которым я сейчас занимаюсь, с выключенным open session in view).