假设我有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 子查询不允许选择多列。还有其他方法可以做到这一点吗?
要在不使用 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
对象。