Trong thuật ngữ Hibernate, bag là một List không có thứ tự (không @OrderColumn).
Khi bạn fetch hai bag trong cùng một truy vấn, Hibernate từ chối chạy:
@Query("select p from Post p join fetch p.comments join fetch p.tags") // throwsLý do: join hai collection tạo ra tích Descartes — 10 comment và 5 tag thành 50 dòng. Bag không loại trùng và không có thứ tự nên Hibernate không có cách nào ánh xạ đúng dòng nào về phần tử nào. Nó chọn ném exception thay vì trả dữ liệu nhân bản âm thầm.
Ba hướng xử lý, theo thứ tự nên ưu tiên:
1. Tách thành nhiều truy vấn — tốt nhất về hiệu năng vì tránh hẳn tích Descartes. Chạy hai truy vấn, mỗi truy vấn fetch một collection; persistence context tự gộp về cùng object Post.
List<Post> posts = em.createQuery("select distinct p from Post p join fetch p.comments where p.id in :ids", Post.class)
.setParameter("ids", ids).getResultList();
em.createQuery("select distinct p from Post p join fetch p.tags where p.id in :ids", Post.class)
.setParameter("ids", ids).getResultList(); // same Post instances get their tags filled2. Đổi List sang Set — hết exception vì Set không phải bag. Nhưng SQL vẫn là tích Descartes: 10 comment × 5 tag = 50 dòng phải truyền và khử trùng ở phía ứng dụng. Chấp nhận được khi collection nhỏ.
3. @OrderColumn — biến bag thành list có thứ tự, nhưng đòi thêm cột index và làm chậm thao tác chèn/xoá giữa danh sách.
Dùng Set là cách sửa nhanh nhất; tách truy vấn là cách đúng khi dữ liệu lớn.