Ответ
Проблема N+1 — это антипаттерн производительности в ORM, когда для загрузки одной коллекции объектов выполняется один основной запрос и N дополнительных запросов (по одному на каждый элемент основной выборки).
Как возникает:
При ленивой (LAZY) загрузции связанных сущностей Hibernate сначала выполняет запрос для родительских объектов, а затем, при обращении к дочерней коллекции для каждого родителя, генерирует отдельный запрос.
Пример проблемы:
@Entity
public class Author {
@Id
private Long id;
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> books;
}
// HQL-запрос (1 запрос для авторов)
List<Author> authors = session.createQuery("FROM Author", Author.class).list();
// При обходе для каждого автора выполняется отдельный запрос для книг (N запросов)
for (Author author : authors) {
System.out.println(author.getBooks().size()); // Триггер N+1!
}
// ИТОГО: 1 (авторы) + N (книги для каждого автора) запросов.
Решения:
JOIN FETCHв JPQL/HQL:String jpql = "SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books"; List<Author> authors = session.createQuery(jpql, Author.class).getResultList();@EntityGraph(JPA 2.1):@EntityGraph(attributePaths = {"books"}) @Query("SELECT a FROM Author a") List<Author> findAllWithBooks();- Пакетная загрузка (
@BatchSize):@OneToMany(mappedBy = "author") @BatchSize(size = 10) private List<Book> books; // Hibernate загрузит книги для авторов пачками (например, по 10)
Критерий выбора: JOIN FETCH для небольшого известного набора данных, @BatchSize для больших или вложенных графов.