что такое entitygraph как и для чего их использовать

Entity Graph в Spring Data JPA

С помощью Entity Graph можно задать для каждого запроса свою стратегию загрузки данных: LAZY либо EAGER. Рассмотрим, как это сделать на примере простой модели — поста с коллекциями картинок и тегов.

Модель

Класс Post с коллекциями:

Теги — просто строки, а картинки — класс:

Нас будет интересовать загрузка коллекций тегов и картинок при выборе (select) поста. Как известно, по умолчанию они загружаются лениво.

@NamedEntityGraphs

Но у нас это не так, поскольку пост аннотирован @NamedEntityGraphs с одним графом @NamedEntityGraph, в котором сказано, что картинки следует загружать EAGER.

attributeNodes

Потому что всё, что указано в attributeNodes будет загружаться EAGER.

Заметьте, что граф у нас один, а можно задать несколько графов @NamedEntityGraph, каждый со своим набором атрибутов. И использовать нужный граф в конкретном запросе.

Ниже рассмотрим, как указать в запросе нужный граф.

Entity Graph в Derived Query Methods

Делается это просто. Причем можно как сослаться на граф, прописанный в аннотации выше, так и создать свой на ходу.

Как сослаться в запросе на существующий граф

Для этого метод репозитория аннотируем с помощью @EntityGraph и укажем внутри имя графа (post-entity-graph — так называется наш граф в аннотации @NamedEntityGraph выше):

Проверим, что при использовании этого метода оператор select выбирает не просто посты, а посты с картинками (так что поле images выбранного Post будет заполнено):

В консоли видим выполняемый select:

В настройках включено:

Но не обязательно аннотировать Post кучей @NamedEntityGraphs, граф можно собрать и на ходу.

Как задать граф с нуля прямо в запросе

Делается это вот таким нехитрым способом:

Теперь мы выбрали теги (tags) в качестве загружаемого EAGER поля, и можно проверить в тесте, что оператор select действительно выбирает теги:

Entity Graph с @Query

Использовать граф с @Query-запросом тоже не проблема:

В консоли опять же получим выбор поста с картинками:

Entity Graph с кастомными методами

Поскольку Entity Graph применим с чистым JPA и Hibernate, с кастомными методами тоже его можно применить не специфичным для Spring способом.

Создадим пользовательский репозиторий с методом:

Как видно в коде выше, граф применяется с помощью метода setHint().

Больше о графах в Hibernate есть тут (а также о типах графа fetch и load). В Spring по умолчанию используется тип графа FETCH.

Как сменить тип графа в Spring с FETCH на LOAD

Если вдруг тип Entity Graph по умолчанию (FETCH) не устраивает, то изменить тип можно так:

Источник

Entity Graph

Существуют две стратегии загрузки полей сущности: FetchType.EAGER и FetchType.LAZY. С помощью Entity Graph можно менять стратегию во время выполнения программы.

Модель

Продолжим эксперименты с нашей моделью (пост с тегами и картинками), только теперь над сущностью Post зададим @NamedEntityGraph:

@NamedEntityGraph

Каждая аннотация @NamedEntityGraph (а их может быть несколько) задает свою стратегию загрузки полей. Те поля, которые перечислены в attributeNodes получают fetch = FetchType.EAGER.

Как работает загрузка по умолчанию без Entity Graph

Известно, что по умолчанию коллекции загружаются лениво (FetchType.LAZY), то есть при выборке постов:

В консоли мы увидим select только из таблицы posttags и images не загружаются):

Теперь с Entity Graph

Если же использовать Entity Graph, то загрузятся еще и коллекции, которые упомянуты в графе в качестве атрибута.

Статический Entity Graph

Сначала используем граф post-entity-graph, который задали в аннотации.

В нем указано, что нужно загружать images, что и происходит:

Динамический Entity Graph

Но не обязательно задавать граф в аннотации, можно сформировать его на ходу. Создадим граф, загружающий теги, и выполним find():

Выше мы задали граф с атрибутом tags.

В консоли видно, что делается выборка не только из таблицы постов Post, но и тегов Post_tags:

До сих пор мы выполняли запросы методом find(). Теперь сделаем то же самое с помощью createQuery():

Как указать Entity Graph в Query

Как видите, граф мы указали в методе setHint().

Типы Entity Graph: fetch vs load

Стоит упомянуть еще о типах графа. До сих пор мы использовали Entity Graph типа fetch. Его конфигурация полностью переопределяет fetching strategy по умолчанию. Это значит, что если бы, например, в описании класса теги были аннотированы с помощью FetchType.EAGER:

то они все равно бы не загружались, если не упомянуты в графе.

Entity Graph же типа load дополняет стратегию по умолчанию. То есть не упомянутые в графе теги загружались бы все равно из-за FetchType.EAGER в описании класса.

Тип графа мы задавали в строках:

Читайте также:  что делать если узнала что беременна а спиртное употребляла

Для графа типа load было бы так:

Протестируем loadgraph. Для этого пометим коллекцию тегов (fetch = FetchType.EAGER):

А затем извлечем посты с loadgraph, при этом в граф не добавляем никаких атрибутов:

Несмотря на то, что атрибуты не добавлены в граф, они извлекаются:

Если добавить с тест строку, то загрузятся и теги, и images:

Подграфы

Entity Graph может быть сложным. Если загружаемая сущность Image в свою очередь тоже ссылается на какую-нибудь коллекцию colors или другую сущность, то для нее тоже можно задать вложенный Entity Graph:

Итоги

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

Еще об Entity Graph в Spring Data.

Entity Graph: 6 комментариев

Относительно раздела 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).

Источник

Spring Data JPA и именованные графы сущностей

Узнайте, как лучше управлять сущностями, которые вы хотите извлечь, используя Spring JPA.

1. Обзор

Проще говоря, Entity Graphs -это еще один способ описания запроса в JPA 2.1. Мы можем использовать их для формулирования более эффективных запросов.

В этом уроке мы узнаем, как реализовать графики сущностей с помощью Spring Data JPA на простом примере.

2. Сущности

Во-первых, давайте создадим модель под названием Элемент который имеет несколько характеристик:

Теперь давайте определим сущность C characteristic :

3. Графы Сущностей

В Spring Data JPA мы можем определить граф сущностей, используя комбинацию @NamedEntityGraph и @EntityGraph аннотаций . Или мы также можем определить специальные графы сущностей только с помощью аргумента attribute Paths аннотации @EntityGraph .

Давайте посмотрим, как это можно сделать.

3.1. С помощью @NamedEntityGraph

Во-первых, мы можем использовать аннотацию JPA @NamedEntityGraph непосредственно на нашем Item entity:

А затем мы можем прикрепить аннотацию @EntityGraph к одному из наших методов репозитория:

Таким образом, в нашем случае свойство characteristics будет загружаться с нетерпением, даже если стратегия выборки по умолчанию аннотации @OneToMany является ленивой.

Одна загвоздка здесь заключается в том, что если определенная стратегия fetch является НЕТЕРПЕЛИВОЙ, то мы не можем изменить ее поведение на ЛЕНИВОЕ . Это сделано специально, поскольку последующие операции могут нуждаться в нетерпеливо извлеченных данных на более позднем этапе выполнения.

3.2. Без @NamedEntityGraph

Или мы можем определить специальный граф сущностей to, width attribute Paths.

Это будет загружать свойство item объекта Characteristic с нетерпением, даже если наша сущность объявляет стратегию ленивой загрузки для этого свойства.

Это удобно, так как мы можем определить граф сущностей встроенным, а не ссылаться на существующий именованный граф сущностей.

4. Тестовый случай

Теперь когда мы определили наши графы сущностей давайте создадим тестовый случай для его проверки:

Давайте посмотрим на SQL, сгенерированный Hibernate:

Для сравнения давайте удалим аннотацию @EntityGraph из репозитория и проверим запрос:

Наконец, давайте сравним запросы Hibernate второго теста с аннотацией @EntityGraph :

И запрос без аннотации @EntityGraph :

5. Заключение

Источник

Hibernate — о чем молчат туториалы

Эта статья не будет затрагивать основы hibernate (как определить entity или написать criteria query). Тут я постараюсь рассказать о более интересных моментах, действительно полезных в работе. Информацию о которых я не встречал в одной месте.

Сразу оговорюсь. Все ниже изложенное справедливо для Hibernate 5.2. Также возможны ошибки в силу того, что я что-то неправильно понял. Если обнаружите — пишите.

Читайте также:  что делать чтобы вишня хорошо плодоносила

Проблемы отображения объектной модели в реляционную

Но начнем все же с основ ORM. ORM — объектно-реляционное отображение — соответственно у нас есть реляционная и объектная модели. И при отображении одной в другую существуют проблемы, которые нам нужно решить самостоятельно. Давайте их разберем.

Для иллюстрации возьмем следующий пример: у нас есть сущность “Пользователь”, который может быть либо джедаем либо штурмовиком. У джедая обязательно должна быть сила, а у штурмовика специализация. Ниже приведена диаграмма классов.

Проблема 1. Наследование и полиморфные запросы.

В объектной модели есть наследование, а в реляционной нет. Соответственно это и первая проблема — как правильно отобразить наследование в реляционную модель.

Hibernate предлагает 3 варианта ее отображения такой объектной модели:

В этом случае, общие поля и поля наследников лежат в одной таблице. Используя такую стратегию мы избегаем join-ов при выборе сущностей. Из минусов стоит отметить, что во-первых, мы не можем в реляционной модели задать “NOT NULL” ограничение для колонки “force” и во-вторых, мы теряем третью нормальную форму. (появляется транзитивная зависимость неключевых атрибутов: force и disc).

Кстати, в том числе и по этой причине есть 2 способа указать not null ограничение у поля — NotNull отвечает за валидацию; @Column(nullable = true) — отвечает за not null ограничение в базе данных.

В этом случае у нас нет общей таблицы. Используя эту стратегию, при полиморфных запросах мы используется UNION. У нас появляеются проблемы с генераторами первичных ключей и другими ограничениями целостности. Данный тип отображения наследования строго не рекомендуется использовать.

На всякий случай упомяну про аннотацию — @MappedSuperclass. Она используется когда вы хотите “спрятать” общие поля для нескольких сущностей объектной модели. При этом сам аннотированный класс не рассматривается как отдельная сущность.

Проблема 2. Отношение композиции в ООП

Возвращаясь к нашему примеру заметим, что в объектной модели мы вынесли профиль пользователя в отдельную сущность — Profile. Но в реляционной модели мы не стали выделять под нее отдельную таблицу.

Отношение OneToOne чаще является плохой практикой, т.к. в селекте у нас появляется неоправданный JOIN (даже указав fetchType=LAZY в большинстве случаев у нас JOIN останется — эту проблему обсудим позже).

Для отображения композиции в общую таблицу существуют аннотации @Embedable и @Embeded. Первая ставится над полем, а вторая над классом. Они взаимозаменяемые.

Entity Manager

Каждый экземпляр EntityManager-а (EM) определяет сеанс взаимодействия с базой данных. В рамках экземпляра EM-а, существует кэш первого уровня. Тут я выделю следующие значимые моменты:

Под операцией flush скрывается интересная фича hibernate — он пытается снизить время блокировки строк в БД.

Dirty Checking — это механизм, выполняемый во время операции flush. Его цель найти сущности, которые изменились и обновить их. Чтобы реализовать такой механизм, hibernate должен хранить оригинальную копию объекта (то с чем будет сравниваться актуальный объект). Если быть точнее, то hibernate хранит копию полей объекта, а не сам объект.

Тут стоит отметить, что если граф сущностей большой, то операция dirty checking-а может стоить дорого. Не стоит забывать о том, что hibernate хранит 2 копии сущностей (грубо говоря).
С целью “удешевить” этот процесс пользуйтесь следующими фичами:

Как известно hibernate позволяет обновлять сущности только внутри транзакции. Больше свободы предлагают операции чтения — их мы можем выполнять не открывая явно транзакцию. Но в этом как раз и вопрос, стоит ли для операций чтения открывать транзакцию явно?

Приведу несколько фактов:

Генераторы

Генераторы нужны для описания, каким способом первичные ключи наших сущностей будут получать значения. Давайте быстро пробежимся по вариантам:

Deadlock

Давайте разберем на примере псевдокода ситуацию, которая может привести к deadlock-у:

Для предотвращения таких проблем у hibernate есть механизм, который позволяет избежать deadlock-ов такого типа — параметр hibernate.order_updates. В этом случае все update-ы будут упорядочены по id и выполнены. Также еще раз упомяну, что hibernate старается “отсрочить” захват коннекшена и выполнение insert-ов и update-ов.

Set, Bag, List

В hibernate есть 3 основных способа представить коллекцию связи OneToMany.

Возникает вопрос, а что все-таки лучше использовать bag или set? Начнем с того, что при использовании bag-а возможны следующие проблемы:

Сила References

Reference — это ссылка на объект, загрузку которого мы решили отложить. В случае отношения ManyToOne с fetchType=LAZY, мы получаем такой reference. Инициализация объекта происходит в момент обращения к полям сущности, за исключением id (т.к. значение этого поля нам известно).

Читайте также:  что нужно делать после набития татуировки

Стоит отметить, что в случае Lazy Loading-а reference всегда ссылается на существующую строку в БД. Именно по этой причине большинство случаев Lazy Loading-а в отношениях OneToOne не работает — hibernate необходимо сделать JOIN для проверки существования связи и JOIN уже был, то hibernate загружает его в объектную модель. Если же мы укажем в OneToOne связи nullable=true, то LazyLoad должен заработать.

Мы можем и самостоятельно создать reference, используя метод em.getReference. Правда в таком случае нет гарантии, что reference ссылается на существующую строку в БД.

Давайте приведем пример использования такой ссылки:

На всякий случай напомню, что мы получим LazyInitializationException в случае закрытого EM-а или отсоединенной(detached) ссылки.

Дата и время

Не смотря на то что в java 8 появилось прекрасное API для работы с датой и временем, JDBC API по прежнему позволяет работать только со старым API дат. Поэтому разберем некоторые интересные моменты.

Во-первых, нужно четко понимать отличия LocalDateTime от Instant и от ZonedDateTime. (Не буду растягивать, а приведу отличные статьи на эту тему: первая и вторая)

Более интересный и важный момент — как даты сохраняется в базу данных. Если у нас проставлен тип TIMESTAMP WITH TIMEZONE то проблем быть не должно, если же стоит TIMESTAMP (WITHOUT TIMEZONE) то есть вероятность, что дата запишется/прочитается неверная. (за исключением LocalDate и LocalDateTime)

Давайте разберемся почему:

Когда мы сохраняем дату, используется метод со следующей сигнатурой:

Как видим тут используется старое API. Дополнительный аргумент Calendar нужен для того, чтобы преобразовать timestamp в строковое представление. т.е он хранит в себе timezone-у. Если Calendar не передается, то используется Calendar по-умолчанию с таймзоной JVM.

Решить эту проблему можно 3 способами:

Для ответа на этот вопрос нужно понимать структуру класса java.util.Date (java.sql.Date и java.sql.Timestamp его наследники и их отличия в данном случае нас не волнуют). Date хранит дату в миллисекундах c 1970 года грубо говоря в UTC, но метод toString преобразует дату согласно системной timeZone.

Соответственно, когда мы получаем из базы данных дату без таймзоны, она отображатеся в объект Timestamp, так чтобы метод toString отобразил ее желаемое значение. При этом количество миллисекунд с 1970-го года может отличаться (в зависимости от временной зоны). Именно поэтому только локальное время отображается всегда корректно.

Также привожу в пример код, ответственный за преобразование Timesamp в LocalDateTime и Instant:

Batching

По-умолчанию запросы отправляются в БД по одному. При включении batching-а hibernate сможет в одном запросе к БД отправлять несколько statement-ов. (т.е. batching сокращает количество round-trip-ов к БД)

Для этого необходимо:

N+1 проблема

Это достаточно изъезженная тема, поэтому пробежимся по ней быстро.

N+1 проблема — это ситуация, когда вместо одного запроса на выбор N книг происходит по меньшей мере N+1 запрос.

Самый простой способ решения N+1 проблемы это сделать fetch связанных таблиц. В этом случае у нас может возникнуть несколько других проблем:

Тестирование

В идеале development окружение должно предоставлять как можно больше полезной информации о работе hibernate и о взаимодействии с БД. А именно:

Источник

JPA Entity Graphs

Одной из последних функций в JPA 2.1 является возможность задавать планы выборки с использованием графов объектов. Это полезно, поскольку позволяет настраивать данные, полученные с помощью запроса или операции поиска. При работе с приложениями среднего и большого размера распространено отображение данных из одной и той же сущности разными и разными способами. В других случаях вам просто нужно выбрать наименьший набор информации для оптимизации производительности вашего приложения.

пример

Рассмотрим следующий граф сущностей:

(Вероятно, отношения должны быть от N до N, но давайте сделаем это проще).

А у сущности Кино есть следующее определение:

Аннотации

Entity Graph фильмWithActors

Это определяет Entity Graph с именем movieWithActors и указывает, что отношение movieActors должно быть загружено.

Entity Graph фильмWithActorsAndAwards

Обратите внимание, что мы не указываем атрибут id в графе сущностей. Это потому, что первичные ключи всегда выбираются независимо от того, что указано. Это также верно для атрибутов версии.

Советы

Чтобы использовать графы сущностей, определенные в запросе, вы должны установить их как подсказку. Вы можете использовать два свойства подсказки, и они также влияют на способ загрузки данных.

Для упрощения и на основе нашего примера при применении Entity Graph movieWithActors :

По умолчанию / указано javax.persistence.fetchgraph javax.persistence.loadgraph
movieActors LAZY EAGER EAGER
movieDirectors EAGER LAZY EAGER
movieAwards LAZY LAZY LAZY

запрос

Выполнить запрос легко. Вы делаете это, как обычно, но просто вызываете setHint для объекта Query :

Источник

Строительный портал