Каково решение проблемы N + 1 в JPA и Hibernate?

Я понимаю, что проблема N + 1 заключается в том, что один запрос выполняется для извлечения N записей и N запросов для извлечения некоторых реляционных записей.

Но как этого можно избежать в Hibernate?

Предположим, что у нас есть class-изготовитель со многими отношениями с контактом.

Мы решаем эту проблему, убедившись, что исходный запрос извлекает все данные, необходимые для загрузки объектов, которые нам нужны, в их надлежащим образом инициализированном состоянии. Один из способов сделать это – использовать соединение для получения HQL. Мы используем HQL

"from Manufacturer manufacturer join fetch manufacturer.contact contact" 

с помощью инструкции выборки. Это приводит к внутреннему соединению:

 select MANUFACTURER.id from manufacturer and contact ... from MANUFACTURER inner join CONTACT on MANUFACTURER.CONTACT_ID=CONTACT.id 

Используя запрос Criteria, мы можем получить тот же результат из

 Criteria criteria = session.createCriteria(Manufacturer.class); criteria.setFetchMode("contact", FetchMode.EAGER); 

который создает SQL:

 select MANUFACTURER.id from MANUFACTURER left outer join CONTACT on MANUFACTURER.CONTACT_ID=CONTACT.id where 1=1 

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

для получения дополнительной информации здесь ссылка на проблему и решение

Нативное решение для 1 + N в Hibernate называется:

20.1.5. Использование пакетной выборки

Используя пакетную выборку, Hibernate может загружать несколько неинициализированных прокси-серверов, если один прокси-сервер обращается к нему. Пакетная выборка – это оптимизация ленивой страtagsи выборки. Существует два способа настройки пакетной выборки: на уровне 1) и 2) уровне сбора …

Проверьте эти вопросы и ответы:

  • @BatchSize, но много кругосветное путешествие в случае @ManyToOne
  • Избегание n + 1 нетерпеливой выборки ассоциации элементов коллекции

С аннотациями мы можем сделать это следующим образом:

Уровень class :

 @Entity @BatchSize(size=25) @Table(... public class MyEntity implements java.io.Serializable {... 

Уровень collection :

 @OneToMany(fetch = FetchType.LAZY...) @BatchSize(size=25) public Set getMyColl() 

Ленивая загрузка и пакетная выборка вместе представляют собой оптимизацию, которая:

  • не требует явной выборки в наших запросах
  • будет применяться к любому количеству ссылок, которые (лениво) затрагиваются после загрузки корневого объекта (при явном извлечении эффектов только эти имена в запросе)
  • будет решать проблему 1 + N с коллекциями (потому что только одна коллекция может быть извлечена с помощью корневого запроса) без необходимости дальнейшей обработки Чтобы получить корневые значения DISTINCT (проверьте: Criteria.DISTINCT_ROOT_ENTITY vs Projections.distinct )

Проблема

Проблема с запросом N + 1 возникает, когда вы забываете получить ассоциацию, а затем вам нужно получить к ней доступ.

Например, предположим, что у нас есть следующий запрос JPA:

 List comments = entityManager.createQuery( "select pc " + "from PostComment pc " + "where pc.review = :review", PostComment.class) .setParameter("review", review) .getResultList(); 

Теперь, если мы итерируем объекты PostComment и пересекаем ассоциацию сообщений:

 for(PostComment comment : comments) { LOGGER.info("The post title is '{}'", comment.getPost().getTitle()); } 

Hibernate будет генерировать следующие SQL-операторы:

 SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_ FROM post_comment pc WHERE pc.review = 'Excellent!' INFO - Loaded 3 comments SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_ FROM post pc WHERE pc.id = 1 INFO - The post title is 'Post nr. 1' SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_ FROM post pc WHERE pc.id = 2 INFO - The post title is 'Post nr. 2' SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_ FROM post pc WHERE pc.id = 3 INFO - The post title is 'Post nr. 3' 

Вот как генерируется запрос N + 1.

Поскольку ассоциация post не инициализируется при получении объектов PostComment , Hibernate должен получить объект Post с вторичным запросом, а для N объектов PostComment будет выполнено еще N запросов (следовательно, проблема с запросом N + 1).

Исправить

Первое, что вам нужно сделать, чтобы решить эту проблему, – это добавить правильное ведение журнала и мониторинг SQL . Без ведения журнала вы не заметите проблему с запросом N + 1 при разработке определенной функции.

Во-вторых, чтобы исправить это, вы можете просто подключить FETCH к отношениям, вызывающим эту проблему:

 List comments = entityManager.createQuery( "select pc " + "from PostComment pc " + "join fetch pc.post p " + "where pc.review = :review", PostComment.class) .setParameter("review", review) .getResultList(); 

Если вам нужно получить несколько дочерних ассоциаций, лучше получить одну коллекцию в исходном запросе, а вторую – с вторичным SQL-запросом.

Эта проблема лучше всего улавливается интеграционными тестами. Вы можете использовать автоматическое утверждение JUnit для проверки ожидаемого количества сгенерированных операторов SQL . Проект db-unit уже предоставляет эту функциональность, и он является открытым исходным кодом, а зависимость доступна на Maven Central.

  • Родные запросы JPA / Hibernate не распознают параметры
  • Entity Framework слишком медленная. Какие у меня варианты?
  • Dapper.Rainbow VS Dapper.Contrib
  • Entity Framework .Remove () vs. .DeleteObject ()
  • Любые хорошие инструменты ORM для разработки Android?
  • В чем разница между JPA и Hibernate?
  • Как выполнить неполиморфный запрос HQL в Hibernate?
  • mappedBy ссылается на неизвестное свойство целевого объекта
  • В чем разница между @JoinColumn и mappedBy при использовании ассоциации JPA @OneToMany
  • Hibernate / JPA - аннотирование методов бобов и полей
  • Пакетные вставки с JPA / EJB3
  • Давайте будем гением компьютера.