Gymterview
middle

Что такое проблема N+1 и как её решить?

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

Пример проблемы

Пример
// 1 запрос: SELECT * FROM users
List<User> users = userRepository.findAll();

for (User user : users) {
    // N запросов: SELECT * FROM orders WHERE user_id = ?
    System.out.println(user.getOrders().size());
}
// Итого: 1 + N запросов. При 1000 пользователей -- 1001 запрос!

Решения

1. JOIN FETCH (JPQL)

Пример
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.status = :status")
List<User> findByStatusWithOrders(@Param("status") String status);
// 1 запрос с JOIN

2. @EntityGraph

Пример
@EntityGraph(attributePaths = {"orders"})
List<User> findByStatus(String status);

3. Batch fetching

Пример
spring.jpa.properties.hibernate.default_batch_fetch_size=25

Вместо N запросов будет ceil(N/25) запросов с WHERE user_id IN (?, ?, ..., ?).

4. DTO-проекция (лучшая производительность)

Пример
@Query("SELECT new com.example.dto.UserOrderSummary(u.id, u.name, COUNT(o)) " +
       "FROM User u LEFT JOIN u.orders o GROUP BY u.id, u.name")
List<UserOrderSummary> findUserOrderSummaries();

Как обнаружить

Пример
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG

На собеседовании: назовите проблему, покажите пример и минимум 2-3 решения. Частая ошибка – использовать JOIN FETCH с пагинацией (Hibernate загрузит все данные в память). Решение – @BatchSize или подзапрос.