【问题标题】:Build criteria API query to avoid MultipleBagFetchException构建标准 API 查询以避免 MultipleBagFetchException
【发布时间】:2021-05-17 16:00:21
【问题描述】:

我们有一个课程实体,每个课程都有参加课程的学生和嘉宾名单:

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) 帖子 (Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags) 的说法,击败 MultipleBagFetchException 的正确方法是进行两个单独的查询并一个接一个地获取集合。

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

【问题讨论】:

    标签: java hibernate jpa criteria-api


    【解决方案1】:

    根据文章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();
        }
    }
    

    【讨论】:

      【解决方案2】:

      我认为您不需要在查询中获取学生和访客。
      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();
          }
      

      【讨论】:

      • 感谢您的回答,但很遗憾没有 - JPA 不获取这些集合。可能是因为我的 ONE 端没有 FetchType.EAGER 注释。
      • 但是如果您访问了这些属性,那么 JPA 会为您加载它们。您正在加载“父”实体(课程)。如果/当您需要学生和客人时,您可以简单地为他们调用 getter,您的 JPA-Provider 将为您加载它们。使用 BatchSize 注释(设置为相当大的值),您可以在一个查询中为所有课程加载它们。这意味着您需要三个查询来加载所有数据,假设课程的数量小于您的 BatchSize。课程 1 个,所有学生 1 个,所有客人 1 个。
      【解决方案3】:

      对于studentsguests,只需使用Set 而不是List

      【讨论】:

      • 据我所知,使用 set 来击败 MultipleBagFetchException 是一种反模式
      • 谁告诉你的,可能是因为错误的原因。当然,使用Set 而不是List 只会解决为其创建异常的Java 对象模型中的基数问题。您可能面临的潜在问题是,如果您正在连接获取嵌套集合,则 JDBC 结果集的大小会增加。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2023-03-12
        • 1970-01-01
        • 2011-11-05
        • 2016-04-04
        • 2012-12-01
        • 2017-07-21
        • 2015-04-12
        • 1970-01-01
        • 2021-02-02
        相关资源
        最近更新 更多