JPA Criteria API 与子查询连接

问题描述 投票:0回答:1

假设我有3节课

class Student {
  @Id
  private Long id;
  private String name;

  @ManyToOne(mappedBy = "student")
  private Set<TestResult> testResults;

  @OneToOne(mappedBy = "student")
  private StudentCard studentCard;
}
class TestResult {
  @Id
  private Long id;

  @ManyToOne
  @JoinColumn(name = "studentId")
  private Student student;

  private String mark;
}
class StudentCard {
  @Id
  private Long id;

  @OneToOne
  @JoinColumn(name = "studentId")
  private Student student;

  private String cardNumber;
}

现在我想要一个查询,选择学生 ID、姓名、考试成绩列表、学生卡号。 我无法真正将结果映射到列表中,因此我创建了一个 DTO 来选择 mark_str 作为带有

,
分隔符的字符串,然后将它们拆分为列表

class StudentInfoDTO {
  private Long id;
  private String name;
  private String markStr;
  private String studentCardNumber;
}

到目前为止我的代码:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<StudentInfoDTO> cr = cb.createQuery(StudentInfoDTO.class);
Root<Student> studentRoot = cr.from(Student.class);

Join testResultJoin = studentRoot.join("testResults", JoinType.LEFT);
Join studentCardJoin = studentRoot.join("studentCard", JoinType.LEFT);

Expression<String> getMarkFunction = cb.function(
                "string_agg",
                String.class,
                testResultJoin.get("mark"),
                cb.literal(",")
        );

cr.select(
  cb.construct(
    StudentInfoDTO.class,
    studentRoot.get("id"),
    studentRoot.get("name"),
    getMarkFunction,
    studentCardJoin.get("cardNumber")
  )
);
cr.groupBy(studentRoot.get("id"),studentRoot.get("name"), studentCardJoin.get("cardNumber"));

List<StudentInfoDTO> results = entityManager.createQuery(cr).getResultList();

这是可行的,但是 string_agg 函数返回多个重复的结果,并且我添加的选择列越多,我必须在 groupBy 中添加的内容就越多。假设我添加了更多的类,并且每个类又多了 3 个字段,那么查询就会变得非常混乱。 有没有办法使用 JPA criteria API 生成此 sql?

select s.id, s.name, trj.mark_str, sc.card_number
from students s
left join (select tr.student_id, string_agg(tr.mark, ',') as mark_str
           from test_results tr
           group by tr.student_id
           ) trj
on s.id = trj.student_id
left join student_cards sc
on s.id = sc.student_id;

据我所知,我无法加入子查询,因为 JPA 子查询不允许选择多列。还有其他方法可以做到这一点吗?

java postgresql jpa criteria-api
1个回答
0
投票

要在不使用 JPA Criteria API 中的子查询且不获得重复结果的情况下获得所需结果,您可以结合使用多个联接和获取以及 group by 子句。您可以通过以下方式修改代码来实现此目的:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<StudentInfoDTO> cr = cb.createQuery(StudentInfoDTO.class);
Root<Student> studentRoot = cr.from(Student.class);

Join<Student, TestResult> testResultJoin = studentRoot.join("testResults", JoinType.LEFT);
Join<Student, StudentCard> studentCardJoin = studentRoot.join("studentCard", JoinType.LEFT);

// Construct a map to hold student id and concatenated marks
Map<Long, String> marksMap = new HashMap<>();

// Fetch the necessary data and populate the marksMap
List<Object[]> resultList = entityManager.createQuery(cr.multiselect(
        studentRoot.get("id"),
        studentRoot.get("name"),
        testResultJoin.get("mark"),
        studentCardJoin.get("cardNumber")
    ).getResultList();

for (Object[] result : resultList) {
    Long studentId = (Long) result[0];
    String mark = (String) result[2];
    
    if (!marksMap.containsKey(studentId)) {
        marksMap.put(studentId, mark);
    } else {
        String existingMarks = marksMap.get(studentId);
        marksMap.put(studentId, existingMarks + "," + mark);
    }
}

// Construct StudentInfoDTO objects using the populated marksMap
List<StudentInfoDTO> results = new ArrayList<>();
for (Object[] result : resultList) {
    Long studentId = (Long) result[0];
    String name = (String) result[1];
    String cardNumber = (String) result[3];
    
    String markStr = marksMap.get(studentId);
    
    StudentInfoDTO studentInfoDTO = new StudentInfoDTO(studentId, name, markStr, cardNumber);
    results.add(studentInfoDTO);
}

return results;

这种方法避免使用子查询并确保您不会得到重复的结果。它首先获取必要的数据并使用学生 ID 和串联标记填充地图。然后,它使用填充的地图构造

StudentInfoDTO
对象。

© www.soinside.com 2019 - 2024. All rights reserved.