Cả hai đều nói với Hibernate "nạp luôn association này trong cùng một câu SQL", nhưng khai báo ở tầng khác nhau.
join fetch nằm trong JPQL — gắn chặt với truy vấn đó:
@Query("select distinct o from Order o join fetch o.items where o.status = :status")
List<Order> findWithItems(@Param("status") OrderStatus status);@EntityGraph là fetch plan khai báo tách rời, dán lên method — kể cả derived query hay method kế thừa từ JpaRepository:
@EntityGraph(attributePaths = { "items", "customer" })
List<Order> findByStatus(OrderStatus status);
@EntityGraph(attributePaths = "items")
@Override
Optional<Order> findById(Long id);Chọn thế nào:
- Cần override fetch plan cho method có sẵn (findById, derived query) mà không muốn viết JPQL → @EntityGraph.
- Truy vấn đã phải viết tay vì where/group by phức tạp → join fetch cho gọn một chỗ.
- Cần nhiều fetch plan khác nhau cho cùng một entity → khai báo vài @NamedEntityGraph rồi tham chiếu theo tên.
Cả hai đều dính chung hai bẫy: phân trang trên collection fetch bị Hibernate chuyển sang cắt trang trong bộ nhớ, và fetch hai collection kiểu List cùng lúc sẽ ném MultipleBagFetchException.