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

Как построить Entity Graph в JPA

2.7 Senior🔥 141 комментариев
#ORM и Hibernate

Комментарии (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

Лучшие практики

  1. По умолчанию используй FetchType.LAZY
  2. Создавай NamedEntityGraphs для часто используемых комбинаций
  3. Используй ad-hoc графы для одноразовых запросов
  4. Избегай EAGER fetch'а
  5. Тестируй SQL запросы — убедись, что граф работает
  6. Будь осторожен с множественными OneToMany — может быть Cartesian product
  7. Комбинируй EntityGraph с @Query для полного контроля
Как построить Entity Graph в JPA | PrepBro