С помощью 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.