如何使用JPA Criteria API筛选拥有3门及以上课程的学生
实体类参考(多对多关联配置)
先确保你的Student和Course实体正确配置多对多关联:
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id") ) private Set<Course> courses = new HashSet<>(); // 构造器、getter、setter省略 }
@Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String courseName; @ManyToMany(mappedBy = "courses") private Set<Student> students = new HashSet<>(); // 构造器、getter、setter省略 }
正确的JPA Criteria API实现
要查询拥有3门及以上课程的学生,核心是通过分组(GROUP BY) + 课程数量统计(COUNT) + HAVING条件过滤实现,以下是可运行的完整代码:
public List<Student> findStudentsWithAtLeast3Courses(EntityManager em) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Student> cq = cb.createQuery(Student.class); Root<Student> studentRoot = cq.from(Student.class); // 关联Course表,用INNER JOIN只保留有课程的学生 Join<Student, Course> courseJoin = studentRoot.join("courses", JoinType.INNER); // 按Student主键分组,确保每个学生仅被统计一次 cq.groupBy(studentRoot.get("id")); // 过滤课程数量≥3的学生 cq.having(cb.ge(cb.count(courseJoin), 3L)); return em.createQuery(cq).getResultList(); }
常见错误分析
- 未添加分组逻辑:如果漏掉
groupBy,会统计所有学生的课程总数,而非单个学生的课程数,完全不符合需求。 - 重复计数问题:若关联时用
LEFT JOIN且未去重,可能出现重复计数(多对多的Set集合不会重复,但SQL层面逻辑不当仍可能触发),此时可改用cb.countDistinct(courseJoin)确保计数准确。 - 分组字段不完整:如果仅按学生姓名分组,会导致同姓名的学生被合并,必须按主键或唯一标识分组。
- 关联类型错误:用
LEFT JOIN会包含无课程的学生,虽然后续HAVING也能过滤,但INNER JOIN更高效,因为我们只需要有课程的学生。
扩展:同时获取学生与课程数量
如果需要同时返回学生信息和对应的课程数量,可修改查询返回Tuple:
public List<Tuple> findStudentsWithCourseCount(EntityManager em) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Tuple> cq = cb.createTupleQuery(); Root<Student> studentRoot = cq.from(Student.class); Join<Student, Course> courseJoin = studentRoot.join("courses", JoinType.INNER); cq.groupBy(studentRoot.get("id"), studentRoot.get("name")); cq.having(cb.ge(cb.count(courseJoin), 3L)); // 同时选择学生对象和课程数量 cq.multiselect(studentRoot, cb.count(courseJoin).alias("courseCount")); return em.createQuery(cq).getResultList(); }
使用时通过tuple.get(Student.class)获取学生,tuple.get("courseCount")获取对应课程数量。
内容的提问来源于stack exchange,提问作者Petr Kostroun
相关产品推荐
相关产品推荐

