You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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();
    }
}

问题分析

原代码的逻辑是:

  1. 子查询提取指定学校+指定年份下有Reports记录的学生ID
  2. 主查询返回指定学校中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:53:16