Entity Graph

Существуют две стратегии загрузки полей сущности: 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 только из таблицы posttags и 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

Подграфы

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")
                                }
                        )
                }
        )
})
В коде коллекция colors отсутствует, выше просто теоретический пример синтаксиса.

Итоги

Код примера есть на GitHub.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *