Проблема N+1
N+1 — классическая ловушка производительности JPA: один запрос на получение списка плюс ещё N запросов на каждую связь. Незаметная, пока данных мало, и катастрофическая на проде.
List<User> users = userRepository.findAll(); // 1 запрос — получили 100 пользователей
for (User user : users) {
System.out.println(user.getOrders().size()); // LAZY — отдельный запрос НА КАЖДОГО пользователя!
}
// итого: 1 + 100 = 101 запрос к базе вместо одного
Каждое обращение к ленивой (LAZY) коллекции у каждого User в цикле триггерит отдельный SQL-запрос — для 100 пользователей это 100 лишних обращений к базе.
Копнуть глубже
Решение — JOIN FETCH, заставляет Hibernate подгрузить связанные данные одним запросом вместе с основным:
@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();
// теперь 1 запрос вместо 101
@EntityGraph — альтернативный способ указать, что подгружать сразу, без написания JPQL вручную:
@EntityGraph(attributePaths = "orders")
List<User> findAll();
Как обнаружить N+1 на практике. Включить логирование SQL-запросов Hibernate (spring.jpa.show-sql=true или, лучше, библиотека datasource-proxy/p6spy) и посмотреть на количество запросов при обработке одного эндпоинта — если видишь подозрительно много однотипных запросов подряд, это N+1. План запроса (EXPLAIN, см. тему «План запроса (EXPLAIN)») здесь не поможет — проблема не в одном запросе, а в их количестве.
• как её решить через JOIN FETCH и как обнаружить на практике (если дошёл до 2-го слоя).