Эффективная выборка дочерних / родительских объектов в двунаправленной связи многие-ко-многим

Я разрабатываю службу SpringBoot, которая использует базу данных mysql.

В моей базе данных есть три таблицы: «Персона», «Получатель» и «Категория». Человек имеет двунаправленную связь «многие ко многим» с категорией, как и получатель. Коллекции загружаются лениво.

В контроллере я хочу дать обзор сущностей в этих трех таблицах. Мой код на уровне сервиса выглядит так:

List<Person> allPersons = personRepository.findAll();
List<Recipient> allRecipients = recipientRepository.findAll();
List<Category> allCategories = categoryRepository.findAll();

for(Person person : allPersons){
    Set<Category> categories = person.getCategories();
    for(Category category : categories){
        // do something with the person and the categories
    }
}

for(Recipient recipient : allRecipients){
    Set<Category> categories = recipient.getCategories();
    for(Category category : categories){
        // do something with the recipient and the categories
    }
}

for(Category category : allCategories){
    Set<Person> persons = category.getPersons();
    for(Person person : persons){
        // do something with the category and the persons
    }
    Set<Recipient> recipients = category.getRecipients();
    for(Recipient recipient : recipients){
        // do something with the category and the recipients
    }
}

В первых трех строках все необходимые сущности загружаются из базы данных ровно один раз в течение трех запросов к базе данных. Это нормально с точки зрения производительности. Но:

Согласно журналам, при звонке, например,

Set<Category> categories = person.getCategories()

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

Например, если в базе данных 5 человек, 6 получателей и 7 категорий, сервис выполняет всего 3 + 5 + 6 + 2 * 7 = 28 запросов к базе данных. Очевидно, это очень неэффективно.

У меня вопрос:

Что мне нужно изменить, чтобы служба извлекала каждую сущность только один раз с минимальным количеством запросов к базе данных?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
40
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

@Query("Select p from Person p join fetch p.categories categories)
public List<Person> fetchAllPerson();

Подробнее об этом можно прочитать Проблема с JPA Hibernate n + 1 (Lazy & Eager Diff)

Спасибо за Ваш ответ! Я попробовал ваше решение, и теперь выполняется ровно три запроса к базе данных для получения всех сущностей. Однако человек, не связанный ни с одной категорией, не получает этого запроса. Я изменил "присоединение к выборке" на "левое соединение с выборкой", что решило проблему. Скажите, пожалуйста, здесь неправильно используется "выборка из левого соединения". Еще один вопрос относительно вашего решения: правильно ли, что каждая сущность НЕ выбирается только один раз из-за двунаправленной связи? Если да, то можно ли получить каждый объект только один раз?

Florian 01.10.2018 17:06

Чтобы сделать приложение быстрее, вам нужно делать меньше запросов. И вам также понадобится тот человек, с которым не связана категория. Итак, левое внешнее соединение - правильный выбор

Ishant Gaurav 01.10.2018 17:33

Для тестирования я добавил в свою базу данных четырех человек и одну категорию и связал их. Когда я выполняю код из моего первого сообщения с помощью решения «выборка с левым соединением», то во внешнем цикле для категорий обнаруживаются четыре категории. В самой базе данных сохраняется только одна категория. Есть ли у вас какие-либо идеи?

Florian 01.10.2018 18:16

Изменение типа allPersons, allRecipients, allCategories, а также типов возвращаемых значений методов репозитория на Set вместо List устранило проблему.

Florian 01.10.2018 18:39

Другие вопросы по теме