Что такое проблема N+1 запроса в ORM?

«Что такое проблема N+1 запроса в ORM?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Как возникает:

  1. Загружается список из N корневых сущностей (1 запрос).
  2. При обращении к лениво загружаемой коллекции (например, author.getBooks()) для каждой из N сущностей выполняется отдельный запрос к БД для получения её связанных данных.

Пример на Hibernate/JPA:

@Entity
public class Author {
    @Id
    private Long id;
    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY) // Ленивая загрузка по умолчанию
    private List<Book> books;
    // getters/setters
}

// Проблемный код
List<Author> authors = entityManager.createQuery("SELECT a FROM Author a", Author.class).getResultList(); // 1 запрос
for (Author a : authors) {
    // Для каждого автора — отдельный запрос к таблице Book
    System.out.println(a.getBooks().size()); // +N запросов
}
// ИТОГО: 1 + N запросов

Решения:

  • Использовать JOIN FETCH в JPQL:
    List<Author> authors = entityManager.createQuery(
        "SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books", 
        Author.class
    ).getResultList(); // Все данные загружаются одним запросом
  • Использовать @NamedEntityGraph или динамический EntityGraph.
  • Настроить пакетную ленивую загрузку (@BatchSize):
    @OneToMany(mappedBy = "author")
    @BatchSize(size = 10)
    private List<Book> books;
    // Hibernate загрузит книги для авторов пачками по 10, а не по одному
  • Для простых случаев использовать проекции (DTO), выбирая только нужные поля.