Bạn vừa kết hợp phân trang với fetch một collection trong cùng một truy vấn:
@Query("select p from Post p join fetch p.comments")
Page<Post> findAllWithComments(Pageable pageable);Vấn đề: join fetch làm mỗi Post bung ra thành nhiều dòng SQL (một dòng cho mỗi comment). Nếu Hibernate đặt LIMIT/OFFSET xuống DB, kết quả sẽ bị cắt giữa chừng các dòng của một post — trả về post kèm chỉ một phần comment, tức là dữ liệu sai. Để giữ đúng, Hibernate kéo toàn bộ result set về JVM rồi mới cắt trang trong bộ nhớ. Bảng vài triệu dòng thì đây là một sự cố bộ nhớ chờ sẵn.
Cách sửa chuẩn — tách hai truy vấn:
// 1. paginate on IDs only, no collection fetch -> real SQL LIMIT/OFFSET
@Query("select p.id from Post p where p.status = :status")
Page<Long> findIds(@Param("status") PostStatus status, Pageable pageable);
// 2. fetch full entities for that page, no pagination involved
@Query("select distinct p from Post p join fetch p.comments where p.id in :ids")
List<Post> findWithComments(@Param("ids") List<Long> ids);Các hướng khác:
- Không cần collection trên trang danh sách thì bỏ fetch, dùng DTO projection.
- Nạp collection theo lô bằng @BatchSize để giảm số truy vấn thay vì join fetch.
- Bật hibernate.query.fail_on_pagination_over_collection_fetch=true để lỗi này ném exception ngay thay vì chỉ cảnh báo trong log rồi lọt lên production.