我们有一个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抛出MultipleBagFetchException-不能同时获取多个包)击败MultipleBagFetchException的正确方法是进行两个单独的查询并一个接一个地获取集合。
但我无法理解如何使用标准API构建这样两个查询,一个接一个地获取集合(我需要使用标准API,因为我在这里举了一些非常简单的代码作为例子,在真实的应用程序中,我有复杂的过滤器,我使用许多谓词来构建查询)。
基于本文的建议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();
}
}
我认为您不需要在查询中获取学生和访客
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();
}
在@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();
}
只需对students
和guests
使用Set
而不是List
。