С помощью Entity Graph можно задать для каждого запроса свою стратегию загрузки данных: LAZY либо EAGER. Рассмотрим, как это сделать на примере простой модели — поста с коллекциями картинок и тегов.
Модель
Класс Post с коллекциями:
@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<>();
//getters/setters/constructors
}
Теги — просто строки, а картинки — класс:
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String url;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
//getter/setter/constructor
}
Нас будет интересовать загрузка коллекций тегов и картинок при выборе (select) поста. Как известно, по умолчанию они загружаются лениво.
@NamedEntityGraphs
Но у нас это не так, поскольку пост аннотирован @NamedEntityGraphs с одним графом @NamedEntityGraph, в котором сказано, что картинки следует загружать EAGER.
attributeNodes
Потому что всё, что указано в attributeNodes будет загружаться EAGER.
Заметьте, что граф у нас один, а можно задать несколько графов @NamedEntityGraph, каждый со своим набором атрибутов. И использовать нужный граф в конкретном запросе.
Ниже рассмотрим, как указать в запросе нужный граф.
Entity Graph в Derived Query Methods
Делается это просто. Причем можно как сослаться на граф, прописанный в аннотации выше, так и создать свой на ходу.
Как сослаться в запросе на существующий граф
Для этого метод репозитория аннотируем с помощью @EntityGraph и укажем внутри имя графа (post-entity-graph — так называется наш граф в аннотации @NamedEntityGraph выше):
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {
@EntityGraph(value = "post-entity-graph")
List<Post> findByTitle(String title);
//другие методы
}
Проверим, что при использовании этого метода оператор select выбирает не просто посты, а посты с картинками (так что поле images выбранного Post будет заполнено):
@Test
@DisplayName("если использовать готовый EntityGraph, images загружаются")
public void givenEntityGraph_whenFind_thenImagesAreEager() {
List<Post> posts = postRepository.findByTitle( "topic1");
}
В консоли видим выполняемый select:
select post0_.id as id1_1_0_, images1_.id as id1_0_1_,
post0_.title as title2_1_0_, images1_.post_id as post_id3_0_1_,
images1_.url as url2_0_1_, images1_.post_id as post_id3_0_0__, images1_.id as id1_0_0__
from post post0_ left outer join image images1_
on post0_.id=images1_.post_id where post0_.title=?
В настройках включено:
spring.jpa.show-sql=true
Но не обязательно аннотировать Post кучей @NamedEntityGraphs, граф можно собрать и на ходу.
Как задать граф с нуля прямо в запросе
Делается это вот таким нехитрым способом:
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {
// метод
@EntityGraph(attributePaths = {"tags"})
Optional<Post> findById(Long id);
//еще метод
}
Теперь мы выбрали теги (tags) в качестве загружаемого EAGER поля, и можно проверить в тесте, что оператор select действительно выбирает теги:
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=?
Entity Graph с @Query
Использовать граф с @Query-запросом тоже не проблема:
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {
//метод
//метод
@EntityGraph(value = "post-entity-graph")
@Query("SELECT p FROM Post p where p.id=:id")
Post getPostWithImages(Long id);
}
В консоли опять же получим выбор поста с картинками:
select post0_.id as id1_1_0_, images1_.id as id1_0_1_, post0_.title as title2_1_0_,
images1_.post_id as post_id3_0_1_, images1_.url as url2_0_1_,
images1_.post_id as post_id3_0_0__, images1_.id as id1_0_0__
from post post0_ left outer join image images1_
on post0_.id=images1_.post_id where post0_.id=?
Entity Graph с кастомными методами
Поскольку Entity Graph применим с чистым JPA и Hibernate, с кастомными методами тоже его можно применить не специфичным для Spring способом.
Создадим пользовательский репозиторий с методом:
public class PostRepositoryImpl implements PostRepositoryCustom {
@PersistenceContext
private EntityManager em;
@Override
public List<Post> findByTitleDesc(String title) {
return em.createQuery("SELECT p FROM Post p WHERE p.title = :title order by p.title desc", Post.class)
.setParameter("title", title)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("post-entity-graph"))
.getResultList();
}
}
Как видно в коде выше, граф применяется с помощью метода setHint().
SQL:
select post0_.id as id1_1_0_, images1_.id as id1_0_1_, post0_.title as title2_1_0_, images1_.post_id as post_id3_0_1_, images1_.url as url2_0_1_, images1_.post_id as post_id3_0_0__, images1_.id as id1_0_0__ from post post0_ left outer join image images1_ on post0_.id=images1_.post_id where post0_.title=? order by post0_.title desc
Больше о графах в Hibernate есть тут (а также о типах графа fetch и load). В Spring по умолчанию используется тип графа FETCH.
Как сменить тип графа в Spring с FETCH на LOAD
Если вдруг тип Entity Graph по умолчанию (FETCH) не устраивает, то изменить тип можно так:
@EntityGraph(value = "post-entity-graph", type = EntityGraph.EntityGraphType.LOAD) List<Post> findByTitle(String title);
Итоги
Код примера доступен на GitHub.