2

We have a Lesson entity, every Lesson has lists of students and guests which attend the lesson:

public class Lesson {
    @Id
    private Long id;
    // ...other properties
    @OneToMany(mappedBy = "lesson", cascade = CascadeType.ALL)
    private List<Student> students;
    @OneToMany(mappedBy = "lesson", , cascade = CascadeType.ALL)
    private List<Guest> guests;
    // ...constructors, getters and setters
}
// --------------------------------------------------------------------------------------------
public class Student {
    @Id
    private Long id;
    // ...
    @ManyToOne
    @JoinColumn(name = "lesson_id")
    private Lesson lesson;
    // ...
}
// -------------------------------------------------------------------------------------------
public class Guest {
    @Id
    private Long id;
    // ...
    @ManyToOne
    @JoinColumn(name = "lesson_id")
    private Lesson lesson;
    // ...
}

I want to get all lessons with students and guests fetched so I build the following queries with criteria API:

@Repository
public class LessonCriteriaRepositoryImpl implements LessonCriteriaRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public List<Lesson> findAll() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Lesson> criteriaQuery = builder.createQuery(Lesson.class);
        Root<Lesson> lesson = criteriaQuery.from(Lesson.class);
        lesson.fetch(Lesson_.students, JoinType.LEFT);
        lesson.fetch(Lesson_.guests, JoinType.LEFT);
        criteriaQuery.select(lesson).distinct(true);
        TypedQuery<Lesson> query = em.createQuery(criteriaQuery);
        return query.getResultList();
    }
}

and I get MultipleBagFetchException because I cannot fetch several collections at one time. According to Vlad Mihalcea (https://twitter.com/vlad_mihalcea) post (Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags) the proper way to beat MultipleBagFetchException is making two separate queries and fetch collections one after another.

But I cannot understand how to build such two queries fetching collections one after another using criteria API. (I need to use criteria API because I gave as an example here some very simplified code, in real app I have complex filters and I use many predicates to build a query).

dimmxx
  • 131
  • 1
  • 10

4 Answers4

4

Based on the recommendations of the article https://vladmihalcea.com/hibernate-multiplebagfetchexception/, we can build two queries like this:

@Repository
public class LessonCriteriaRepositoryImpl implements LessonCriteriaRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Lesson> findAll() {
        //build first query for fetching students
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Lesson> criteriaQuery = builder.createQuery(Lesson.class);
        Root<Lesson> lesson = criteriaQuery.from(Lesson.class);
        lesson.fetch("students", JoinType.LEFT);
        criteriaQuery.select(lesson).distinct(true);
        TypedQuery<Lesson> query1 = entityManager.createQuery(criteriaQuery);
        List<Lesson> lessons = query1.getResultList();

        //build second query for fetching guests
        builder = entityManager.getCriteriaBuilder();
        criteriaQuery = builder.createQuery(Lesson.class);
        lesson = criteriaQuery.from(Lesson.class);
        lesson.fetch("guests", JoinType.LEFT);
        criteriaQuery.select(lesson).distinct(true).where(lesson.in(lessons));
        TypedQuery<Lesson> query2 = entityManager.createQuery(criteriaQuery);
        return query2.getResultList();
    }
}
saver
  • 2,541
  • 1
  • 9
  • 14
1

I dont't think you need to fetch the students and Guests in your Query.
JPA will take care of filling the Lists in your Lessons.
So this should be enough:

public List<Lesson> findAll() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Lesson> criteriaQuery = builder.createQuery(Lesson.class);
        Root<Lesson> lesson = criteriaQuery.from(Lesson.class);
        criteriaQuery.select(lesson);
        TypedQuery<Lesson> query = em.createQuery(criteriaQuery);
        return query.getResultList();
    }
frank
  • 1,007
  • 1
  • 6
  • 13
  • 1
    Thanks for you answer, but unfortunately no - JPA does not fetch these collections. Maybe because I have no FetchType.EAGER annotations on my ONE side. – dimmxx Feb 16 '21 at 18:12
  • But if you accessed the properties, then JPA would load them for you. You are loading the "parent" entity (Lesson). If / when you want the students and guests, you can simply call the getters for them, and your JPA-Provider will load them for you. Using the BatchSize annotation (set to a reasonably large value), you can load them for all of your Lessons in one query each. That means you need three queries to load all of your data, assuming that the number of Lessons is less than your BatchSize. 1 for the Lessons, 1 for all Students, 1 for all Guests. – Nathan Feb 19 '21 at 19:56
0

add fetch = FetchType.EAGER in @OneToMany annotation,

public class Lesson {
    @Id
    private Long id;
    // ...other properties
    @OneToMany(mappedBy = "lesson",fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Student> students;
    @OneToMany(mappedBy = "lesson",fetch = FetchType.EAGER , cascade = CascadeType.ALL)
    private List<Guest> guests;
    // ...constructors, getters and setters
}

then

public List<Lesson> findAll() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Lesson> criteriaQuery = builder.createQuery(Lesson.class);
        Root<Lesson> lesson = criteriaQuery.from(Lesson.class);
        criteriaQuery.select(lesson);
        TypedQuery<Lesson> query = em.createQuery(criteriaQuery);
        return query.getResultList();
    }
Akash Shah
  • 596
  • 4
  • 17
-1

Just use a Set instead of a List for students and guests.

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58
  • As I know it is an anti pattern to use set to beat MultipleBagFetchException – dimmxx Feb 16 '21 at 18:09
  • 1
    Whoever told you that, it's probably for the wrong reasons. Sure, using a `Set` rather than `List` will only solve the cardinality issues in the Java object model for which the exception was created. The underlying problem you could face is the increase of the size of the JDBC result set if you are join fetching nested collections. – Christian Beikov Feb 17 '21 at 17:17