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à chỗ sớm muộn cũng gây OutOfMemoryError.
Cách sửa chuẩn — tách hai truy vấn:
// 1. phân trang chỉ trên ID, không fetch collection -> LIMIT/OFFSET thật ở SQL
@Query("select p.id from Post p where p.status = :status")
Page<Long> findIds(@Param("status") PostStatus status, Pageable pageable);
// 2. fetch đủ entity cho trang đó, không dính phân trang
@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.