如何使用JPA Criteria API筛选无对应指定年份Reports的Student实体
问题:筛选指定学校和年份下无对应报告的学生列表不符合预期
需求与问题描述
需要筛选出属于指定schoolName,且在指定year没有关联Reports记录的Student实体(Student表无years字段,需通过Reports表的years字段校验)。但现有代码运行后,即使某学生的ID在Reports表中存在(对应其他年份),仍会出现在结果列表中。
现有代码
public List<Student> studentsInReports(String schoolName, String year) { try (Session session = sf.openSession()) { CriteriaBuilder cb = session.getCriteriaBuilder(); CriteriaQuery<Student> cq = cb.createQuery(Student.class); Root<Student> root = cq.from(Student.class); Subquery<Integer> subquery = cq.subquery(Integer.class); Root<Reports> reportsRoot = subquery.from(Reports.class); subquery.select(reportsRoot.join("student").get("id")) .where(cb.and(cb.equal(reportsRoot.get("schoolName"), schoolName), cb.equal(reportsRoot.get("years"), year))); cq.select(root).where(cb.equal(root.get("schoolName"), schoolName), cb.not(root.get("id").in(subquery))); return session.createQuery(cq).getResultList(); } }
问题分析
原代码的逻辑是:
- 子查询提取指定学校+指定年份下有
Reports记录的学生ID - 主查询返回指定学校中ID不在上述子查询结果的学生
这意味着结果会包含两类学生:
- 完全没有任何
Reports记录的学生 - 有
Reports记录,但所有记录都不在指定年份的学生
如果你的真实需求是只筛选完全没有任何Reports记录的指定学校学生,那原代码逻辑不符合预期,因为它会包含有其他年份报告的学生。这种情况需要调整子查询逻辑。
解决方案
情况1:需求是筛选完全无任何Reports记录的指定学校学生
修改子查询,去掉年份条件,提取所有有Reports记录的指定学校学生ID:
public List<Student> studentsInReports(String schoolName, String year) { try (Session session = sf.openSession()) { CriteriaBuilder cb = session.getCriteriaBuilder(); CriteriaQuery<Student> cq = cb.createQuery(Student.class); Root<Student> root = cq.from(Student.class); Subquery<Integer> subquery = cq.subquery(Integer.class); Root<Reports> reportsRoot = subquery.from(Reports.class); // 子查询改为获取指定学校下所有有Reports记录的学生ID subquery.select(reportsRoot.join("student").get("id")) .where(cb.equal(reportsRoot.get("schoolName"), schoolName)); cq.select(root).where(cb.equal(root.get("schoolName"), schoolName), cb.not(root.get("id").in(subquery))); return session.createQuery(cq).getResultList(); } }
情况2:需求是筛选指定学校中指定年份无Reports记录的学生(含其他年份有记录的)
原代码逻辑是正确的,结果中出现有其他年份报告的学生属于符合预期的情况。如果这不是你想要的,说明需求表述存在偏差,需要重新明确筛选规则。
优化建议
使用JPA元模型(如Reports_、Student_)代替字符串属性名,避免拼写错误,同时提升代码类型安全性:
// 需预先生成JPA元模型类 subquery.select(reportsRoot.get(Reports_.student).get(Student_.id)) .where(cb.and(cb.equal(reportsRoot.get(Reports_.schoolName), schoolName), cb.equal(reportsRoot.get(Reports_.years), year))); cq.select(root).where(cb.equal(root.get(Student_.schoolName), schoolName), cb.not(root.get(Student_.id).in(subquery)));
内容的提问来源于stack exchange,提问作者Swapnil
相关产品推荐
相关产品推荐

