@EntityGraph и оптимизированное fetch join. 🧠📌 Что такое N+1?
Когда вам нужно загрузить список сущностей (N штук), а для каждой из них Hibernate выполняет дополнительный запрос к базе — получается 1 запрос для основной выборки + N запросов для связанных данных.
💡 Пример проблемы:
// Репозиторий без оптимизации
public interface UserRepository extends JpaRepository<User, Long> {}
// Сущности
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
При вызове
userRepository.findAll():1. SELECT * FROM users
2. Для каждого пользователя: SELECT * FROM orders WHERE user_id = ?
⚠️ Почему это плохо?
* Избыточные круги по базе
* Замедление при N > 50−100
* Рост нагрузки на БД и время ответа
🔧 Решение #1:
fetch join в @Query
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.active = true")
List<User> findAllActiveWithOrders();
✅ Все нужные данные подтягиваются одним запросом.
❌ Но не всегда удобно, если нужно динамически выбирать связи.
🔧 Решение #2:
@EntityGraph
@Entity
@NamedEntityGraph(
name = "User.withOrders",
attributeNodes = @NamedAttributeNode("orders")
)
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
// ...
}
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(value = "User.withOrders", type = EntityGraph.EntityGraphType.LOAD)
List<User> findAllByActiveTrue();
}
*
@NamedEntityGraph в сущности описывает, какие связи нужно подтянуть* В репозитории вы указываете, какой граф выбирать для метода
* Hibernate формирует один SQL с
LEFT OUTER JOIN, грузя User + Order за 1 запрос🧠 Когда стоит использовать?
* Если часто приходят жалобы на «медленно выбирается список с зависимыми сущностями»
* Когда
fetch = FetchType.LAZY установлен по-умолчанию, но иногда нужна жадная загрузка* Для динамических графов можно строить
EntityGraph прямо в коде через API💡 Код для динамического графа:
public List<User> findActiveWithDynamicGraph() {
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addAttributeNodes("orders", "profile");
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.active = true", User.class
);
query.setHint("javax.persistence.fetchgraph", graph);
return query.getResultList();
}
* Здесь вы сами добавляете, какие связи надо подгрузить
* Подходит, когда нужен гибкий выбор полей «на лету»
⚠️ Важные нюансы:
*
@EntityGraph работает только для root - сущности (не на вложенных коллекциях)* Следите за дублированием строк в результирующем
List: вы можете получить повторяющиеся User - объекты, если у вас несколько Order. В таком случае добавьте DISTINCT в JPQL:
@Query("SELECT DISTINCT u FROM User u JOIN FETCH u.orders WHERE u.active = true")
* Если нужно подгружать множество уровней вложенности, объявите вложенные
subgraph в @NamedEntityGraph📌 Вывод:
1. По умолчанию ставьте
fetch = FetchType.LAZY для всех связей, чтобы не тянуть лишние данные.2. Когда нужна жадная загрузка — используйте
@EntityGraph или fetch join.3. Динамические графы через
EntityManager помогут строить сложные выборки без избыточных запросов.👉 @java_geek