Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как построить Entity Graph в JPA
Entity Graph — это механизм JPA для оптимизации загрузки связанных сущностей. Это решает проблему N+1 queries, позволяя явно указать, какие связи должны быть загружены за один запрос.
Проблема: N+1 Query Problem
// Сущность User с коллекцией Orders
@Entity
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}
@Entity
public class Order {
@Id
private Long id;
@ManyToOne
private User user;
private BigDecimal total;
}
// Проблемный код
List<User> users = userRepository.findAll();
for (User user : users) {
// Для каждого user происходит отдельный SELECT в БД
// 1 SELECT для всех users + N SELECT'ов для каждого user.getOrders()
System.out.println(user.getOrders().size());
}
Этот код выполняет:
- 1 запрос: SELECT * FROM users
- N запросов: SELECT * FROM orders WHERE user_id = ? (для каждого user)
- Всего: N+1 запрос
Способ 1: NamedEntityGraph (Аннотация)
Определить граф на уровне сущности
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "User.withOrders",
attributeNodes = {
@NamedAttributeNode("orders")
}
),
@NamedEntityGraph(
name = "User.complete",
attributeNodes = {
@NamedAttributeNode("orders"),
@NamedAttributeNode("addresses")
}
)
})
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Address> addresses;
}
Использовать в репозитории:
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph("User.withOrders")
List<User> findAll();
@EntityGraph("User.complete")
User findById(Long id);
@EntityGraph("User.withOrders")
@Query("SELECT u FROM User u WHERE u.name LIKE ?1%")
List<User> findByNameStartingWith(String prefix);
}
Результат:
- 1 запрос с LEFT JOIN FETCH
- Все orders загружены сразу
Способ 2: Ad-hoc EntityGraph
Создать граф динамически при вызове
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EntityManager entityManager;
public List<User> getUsersWithOrders() {
// Создать EntityGraph динамически
EntityGraph<User> entityGraph = entityManager.createEntityGraph(User.class);
entityGraph.addAttributeNodes("orders", "addresses");
// Использовать в query
Map<String, Object> hints = new HashMap<>();
hints.put("jakarta.persistence.fetchgraph", entityGraph);
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u",
User.class
);
query.setHint("jakarta.persistence.fetchgraph", entityGraph);
return query.getResultList();
}
}
В Spring Data Repository:
public interface UserRepository extends JpaRepository<User, Long> {
// Spring автоматически создаст EntityGraph
@EntityGraph(
attributePaths = {"orders", "addresses"},
type = EntityGraphType.FETCH
)
List<User> findAll();
// С JPQL
@EntityGraph(attributePaths = {"orders"})
@Query("SELECT u FROM User u WHERE u.active = true")
List<User> findAllActive();
}
Способ 3: Вложенные графы
Загружать связи связей (граф графов)
@Entity
public class Order {
@Id
private Long id;
@ManyToOne
private User user;
@OneToMany(mappedBy = "order")
private List<OrderItem> items;
}
@Entity
public class OrderItem {
@Id
private Long id;
@ManyToOne
private Order order;
@ManyToOne
private Product product;
}
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "Order.withDetails",
attributeNodes = {
@NamedAttributeNode(
value = "items",
subgraph = "items-details"
)
},
subgraphs = {
@NamedSubgraph(
name = "items-details",
attributeNodes = {
@NamedAttributeNode("product")
}
)
}
)
})
public class Order {
// ...
}
Использовать:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order getOrderWithFullDetails(Long orderId) {
// Загружает: Order -> OrderItems -> Products
return orderRepository.findById(
orderId,
"Order.withDetails"
);
}
}
public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph("Order.withDetails")
Optional<Order> findById(Long id);
}
Способ 4: Использование Hint'ов
Явно указать тип fetch'а через hint'ы
@Service
public class UserService {
@Autowired
private EntityManager entityManager;
public User getUserFull(Long userId) {
EntityGraph<User> entityGraph = entityManager.createEntityGraph(User.class);
entityGraph.addAttributeNodes("orders", "addresses");
User user = entityManager.find(
User.class,
userId,
java.util.Map.ofEntries(
java.util.Map.entry(
"jakarta.persistence.fetchgraph",
entityGraph
)
)
);
return user;
}
}
FetchType.EAGER vs EntityGraph
НЕПРАВИЛЬНО: использовать FetchType.EAGER
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER) // ПЛОХО!
private List<Order> orders;
}
Проблемы:
- Orders ВСЕГДА загружаются, даже когда они не нужны
- Может быть несколько OneToMany → Cartesian product
- Не контролируемо
ПРАВИЛЬНО: использовать FetchType.LAZY + EntityGraph
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY) // ХОРОШО
private List<Order> orders;
}
// Когда нужны orders, используй EntityGraph
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = "orders")
List<User> findAll();
}
Полный пример
// Сущности
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "User.withOrdersAndAddresses",
attributeNodes = {
@NamedAttributeNode("orders"),
@NamedAttributeNode("addresses")
}
)
})
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Address> addresses = new ArrayList<>();
}
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private User user;
private BigDecimal total;
}
// Репозиторий
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph("User.withOrdersAndAddresses")
List<User> findAll();
@EntityGraph(
attributePaths = {"orders"},
type = EntityGraphType.FETCH
)
List<User> findByActive(boolean active);
}
// Сервис
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EntityManager entityManager;
// Способ 1: NamedEntityGraph
public List<User> getAllUsers() {
return userRepository.findAll();
}
// Способ 2: Ad-hoc EntityGraph
public User getUserWithDetails(Long userId) {
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addAttributeNodes("orders", "addresses");
User user = entityManager.find(
User.class,
userId,
java.util.Map.of(
"jakarta.persistence.fetchgraph", graph
)
);
return user;
}
// Способ 3: В JPQL запросе
@Transactional(readOnly = true)
public List<User> findActiveUsers() {
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addAttributeNodes("orders");
return entityManager.createQuery(
"SELECT u FROM User u WHERE u.active = true",
User.class
)
.setHint("jakarta.persistence.fetchgraph", graph)
.getResultList();
}
}
EntityGraphType
FETCH vs LOAD
// FETCH: загрузить ТОЛЬКО указанные атрибуты
@EntityGraph(attributePaths = "orders", type = EntityGraphType.FETCH)
List<User> findAll();
// SELECT u.id, u.name, o.id, o.total FROM users u LEFT JOIN orders o
// LOAD: загрузить указанные + другие LAZY атрибуты как обычно
@EntityGraph(attributePaths = "orders", type = EntityGraphType.LOAD)
List<User> findAll();
// SELECT u.id, u.name, o.id, o.total FROM users u LEFT JOIN orders o
// (LOAD часто ведёт себя как FETCH в Hibernate)
Мониторинг SQL запросов
# application.yml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
use_sql_comments: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
Результат без EntityGraph:
-- Запрос 1
SELECT u.id, u.name FROM users u
-- Запрос 2-N
SELECT o.id, o.total FROM orders o WHERE o.user_id = ?
Результат с EntityGraph:
-- Один запрос
SELECT u.id, u.name, o.id, o.total
FROM users u LEFT JOIN orders o ON u.id = o.user_id
Лучшие практики
- По умолчанию используй FetchType.LAZY
- Создавай NamedEntityGraphs для часто используемых комбинаций
- Используй ad-hoc графы для одноразовых запросов
- Избегай EAGER fetch'а
- Тестируй SQL запросы — убедись, что граф работает
- Будь осторожен с множественными OneToMany — может быть Cartesian product
- Комбинируй EntityGraph с @Query для полного контроля