Даниил Дорофеев
Backend-разработчик в SmartData · 10.04
Как я решал проблему N+1 в Hibernate
Разрабатывал Spring Boot-приложение с сущностями Task и TaskDetail (одна задача → много деталей). Столкнулся с классической проблемой N+1 запросов, когда пытался загружать данные.
Как проявлялась проблема
Было 2 сценария: 1. Простой список задач (без деталей) - для табличного отображения 2. Полная задача со всеми деталями - для детальной страницы
Наивная реализация: @Entity public class Task { @Id private Long id;
@OneToMany(mappedBy = "task", fetch = FetchType.LAZY) private List<TaskDetail> details; }
Когда делал так: List<Task> tasks = taskRepository.findAll(); tasks.forEach(task -> { System.out.println(task.getDetails().size()); });
В логах Hibernate (spring.jpa.show-sql=true) видел: Hibernate: select t1_0.id from task t1_0 Hibernate: select d1_0.task_id from task_detail d1_0 where d1_0.task_id=? Hibernate: select d1_0.task_id from task_detail d1_0 where d1_0.task_id=? ... и так для каждой задачи
Для 100 задач → 101 запрос
Как исправил (3 способа)
1. @EntityGraph - точечная жадная загрузка
После изучения проблемы пришел к выводу: - Все ассоциации всегда должны быть LAZY - Жадная загрузка только через @EntityGraph когда действительно нужно
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findAll();
@EntityGraph(attributePaths = "details") List<Task> findAllWithDetails(); }
Как работает @EntityGraph: - Превращает LAZY-ассоциации в FETCH для конкретного запроса - Генерирует LEFT OUTER JOIN - Не влияет на другие методы репозитория
Главные выводы
1. Все ассоциации делаем LAZY по умолчанию 2. Жадная загрузка только через @EntityGraph когда точно нужно 3. JOIN FETCH для сложных кастомных запросов 4. BatchSize как компромисс для ленивой загрузки
еще контент автора
еще контент автора
Даниил Дорофеев
Backend-разработчик в SmartData · 10.04
войдите, чтобы увидеть
и подписаться на интересных профи