构建条件 API 查询以避免 MultipleBagFetchException

Build criteria API query to avoid MultipleBagFetchException

我们有一个 Lesson 实体,每个 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;
    // ...
}

我想获取学生和来宾的所有课程,因此我使用条件 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();
    }
}

我得到了 MultipleBagFetchException,因为我无法一次获取多个集合。根据 Vlad Mihalcea (https://twitter.com/vlad_mihalcea) post (Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags) 的说法,击败 MultipleBagFetchException 的正确方法是进行两个单独的查询并一个接一个地获取集合。

但我不明白如何构建这样的两个查询,使用条件一个接一个地获取集合 API。 (我需要使用标准 API 因为我在这里给出了一些非常简化的代码作为示例,在实际应用程序中我有复杂的过滤器并且我使用许多谓词来构建查询)。

我认为您不需要在查询中获取学生和访客。
JPA 将负责填写您课程中的列表。
所以这应该足够了:

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();
    }

studentsguests.

只需使用 Set 而不是 List

@OneToMany注释中添加fetch = FetchType.EAGER

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
}

然后

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();
    }

根据文章 https://vladmihalcea.com/hibernate-multiplebagfetchexception/ 的建议,我们可以像这样构建两个查询:

@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();
    }
}