← Назад к вопросам

Как работает EntityGraph?

2.0 Middle🔥 221 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Как работает EntityGraph

EntityGraph в JPA/Hibernate — это механизм для оптимизации загрузки связанных сущностей. Он позволяет явно указать, какие отношения нужно загрузить в одном запросе, избегая проблемы N+1 queries.

Проблема, которую решает EntityGraph

N+1 Query Problem (без EntityGraph):

@Entity
public class User {
    @Id
    private Long id;
    
    @OneToMany(fetch = FetchType.LAZY)  // LAZY загрузка
    private List<Order> orders;
}

// Код, который создаёт N+1 запросов
List<User> users = userRepository.findAll();  // 1 запрос: SELECT * FROM users

for (User user : users) {  // users содержит 100 пользователей
    System.out.println(user.getOrders().size());  // 100 дополнительных запросов!
    // SELECT * FROM orders WHERE user_id = ?
}

// Итого: 1 + 100 = 101 запрос!

Это критично для производительности.

Решение 1: Fetch Join (простой способ)

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
    List<User> findAllWithOrders();
}

// Использование
List<User> users = userRepository.findAllWithOrders();  // 1 запрос с JOIN
for (User user : users) {
    System.out.println(user.getOrders().size());  // Данные уже загружены
}

// SQL:
// SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id

Проблема: FETCH JOIN не работает для пагинации.

Решение 2: EntityGraph (универсальный способ)

EntityGraph позволяет определить граф загрузки один раз и переиспользовать его.

Способ 1: Аннотация @NamedEntityGraph

@Entity
@NamedEntityGraph(  // Определяем граф загрузки
    name = "User.withOrders",
    attributeNodes = {
        @NamedAttributeNode(value = "orders")  // Какие связи загружать
    }
)
public class User {
    @Id
    private Long id;
    
    @Column
    private String name;
    
    @OneToMany(fetch = FetchType.LAZY)  // LAZY по умолчанию
    private List<Order> orders;
}

// Использование в репозитории
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // EntityGraph используется через EntityGraph параметр
    @EntityGraph(value = "User.withOrders", type = EntityGraphType.LOAD)
    List<User> findAll();
}

// Результат: 1 LEFT JOIN запрос
// SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id

Способ 2: Динамический EntityGraph

public List<User> findUsersWithOrders() {
    EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
    graph.addAttributeNodes("orders");  // Динамически добавляем атрибут
    
    TypedQuery<User> query = entityManager.createQuery(
        "SELECT u FROM User u",
        User.class
    );
    query.setHint("javax.persistence.loadgraph", graph);
    
    return query.getResultList();
}

EntityGraph типы

1. LOAD (самый частый)

@EntityGraph(value = "User.withOrders", type = EntityGraphType.LOAD)
List<User> findAll();

// Загружаем orders EAGERLY, остальное как в сущности (LAZY)
// SELECT u.*, o.* FROM users u LEFT JOIN orders o

2. FETCH

@EntityGraph(value = "User.withOrders", type = EntityGraphType.FETCH)
List<User> findAll();

// Загружаем ВСЕ как в EntityGraph, остальное не трогаем

Сложные графы (с вложенными отношениями)

@Entity
public class User {
    @Id
    private Long id;
    
    @OneToMany(fetch = FetchType.LAZY)
    private List<Order> orders;
}

@Entity
public class Order {
    @Id
    private Long id;
    
    @ManyToOne
    private User user;
    
    @OneToMany(fetch = FetchType.LAZY)
    private List<OrderItem> items;  // Вложенное отношение
}

// Граф с вложенными узлами
@NamedEntityGraph(
    name = "User.withOrdersAndItems",
    attributeNodes = {
        @NamedAttributeNode(
            value = "orders",
            subgraph = "orders-subgraph"  // Подграф для Order
        )
    },
    subgraphs = {
        @NamedSubgraph(
            name = "orders-subgraph",
            attributeNodes = {
                @NamedAttributeNode("items")  // Загружаем OrderItems
            }
        )
    }
)
public class User {
    // ...
}

// Использование
@EntityGraph("User.withOrdersAndItems", type = EntityGraphType.LOAD)
List<User> findAll();

// SQL: 1 запрос с двумя JOIN
// SELECT u.*, o.*, oi.* FROM users u
// LEFT JOIN orders o ON u.id = o.user_id
// LEFT JOIN order_items oi ON o.id = oi.order_id

Практический пример: E-commerce

@Entity
@NamedEntityGraph(
    name = "User.full",
    attributeNodes = {
        @NamedAttributeNode(value = "orders", subgraph = "orders"),
        @NamedAttributeNode(value = "profile")
    },
    subgraphs = {
        @NamedSubgraph(name = "orders", attributeNodes = {
            @NamedAttributeNode(value = "items"),
            @NamedAttributeNode(value = "payment")
        })
    }
)
public class User {
    @Id
    private Long id;
    
    @OneToMany(fetch = FetchType.LAZY)
    private List<Order> orders;
    
    @OneToOne(fetch = FetchType.LAZY)
    private UserProfile profile;
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @EntityGraph("User.full")
    Page<User> findAll(Pageable pageable);  // Работает с пагинацией!
    
    @EntityGraph("User.full")
    Optional<User> findById(Long id);
}

// Использование
Pageable page = PageRequest.of(0, 10);
Page<User> users = userRepository.findAll(page);  // 1 запрос с 3 JOIN

Производительность: До и После

ДО EntityGraph:

100 пользователей
Запросы:
- 1x SELECT * FROM users          (100ms)
- 100x SELECT * FROM orders      (500ms)
- 500x SELECT * FROM order_items (2000ms)

ИТОГО: 2600ms

ПОСЛЕ EntityGraph:

Запросы:
- 1x SELECT с 3 JOIN (все таблицы)  (50ms)

ИТОГО: 50ms — в 50 раз быстрее!

Когда использовать EntityGraph

Используй:

  • Нужны отношения с LAZY загрузкой
  • Хочешь избежать N+1
  • Работаешь с пагинацией (FETCH JOIN не подходит)
  • Отношения всегда нужны вместе

Не используй если:

  • Отношения редко используются (нарушит оптимизацию)
  • Потом всё равно фильтруешь (JOIN WHERE может быть неэффективен)

Альтернативы

1. FETCH JOIN в @Query

@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();

2. @Transactional(readOnly=true) + LAZY инициализация (опасно)

@Transactional  // Сессия остаётся открытой
public List<User> findUsers() {
    List<User> users = userRepository.findAll();
    users.forEach(u -> u.getOrders().size());  // Инициализируем
    return users;
}

3. DTO Projection (самый безопасный)

@Query("SELECT new com.example.UserDTO(u.id, u.name, o.id, o.amount) " +
       "FROM User u LEFT JOIN u.orders o")
List<UserDTO> findUsersWithOrders();

Итоговый вывод

EntityGraph — мощный инструмент для оптимизации загрузки данных в JPA:

  • Решает проблему N+1 queries
  • Работает с пагинацией (в отличие от FETCH JOIN)
  • Позволяет определить разные графы для разных случаев
  • Может значительно улучшить производительность

Оптимальная стратегия: использовать EntityGraph как основной механизм оптимизации вместе с DTO Projections для сложных случаев.