Карта / JPA / Hibernate / Проблема N+1

Проблема 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)») здесь не поможет — проблема не в одном запросе, а в их количестве.

🎤 Закрыл тему, если можешь объяснить:
• что такое проблема N+1 и почему она возникает с LAZY-связями в цикле;
• как её решить через JOIN FETCH и как обнаружить на практике (если дошёл до 2-го слоя).