Я разрабатываю службу 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 запросов к базе данных. Очевидно, это очень неэффективно.
У меня вопрос:
Что мне нужно изменить, чтобы служба извлекала каждую сущность только один раз с минимальным количеством запросов к базе данных?




Проблема, с которой вы столкнулись, является типичной проблемой спящего режима N + 1. Вы можете быстро получить свою коллекцию и написать свой собственный запрос.
@Query("Select p from Person p join fetch p.categories categories)
public List<Person> fetchAllPerson();
Подробнее об этом можно прочитать Проблема с JPA Hibernate n + 1 (Lazy & Eager Diff)
Чтобы сделать приложение быстрее, вам нужно делать меньше запросов. И вам также понадобится тот человек, с которым не связана категория. Итак, левое внешнее соединение - правильный выбор
Для тестирования я добавил в свою базу данных четырех человек и одну категорию и связал их. Когда я выполняю код из моего первого сообщения с помощью решения «выборка с левым соединением», то во внешнем цикле для категорий обнаруживаются четыре категории. В самой базе данных сохраняется только одна категория. Есть ли у вас какие-либо идеи?
Изменение типа allPersons, allRecipients, allCategories, а также типов возвращаемых значений методов репозитория на Set вместо List устранило проблему.
Спасибо за Ваш ответ! Я попробовал ваше решение, и теперь выполняется ровно три запроса к базе данных для получения всех сущностей. Однако человек, не связанный ни с одной категорией, не получает этого запроса. Я изменил "присоединение к выборке" на "левое соединение с выборкой", что решило проблему. Скажите, пожалуйста, здесь неправильно используется "выборка из левого соединения". Еще один вопрос относительно вашего решения: правильно ли, что каждая сущность НЕ выбирается только один раз из-за двунаправленной связи? Если да, то можно ли получить каждый объект только один раз?