Say I have 3 classes
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;
}
Now I want a query that'll select student id, name, List of test results mark, student card number.
I can't really map the result into List, so I've created a DTO to select the mark_str as string with a , delimeter, and split them later into a List
class StudentInfoDTO {
private Long id;
private String name;
private String markStr;
private String studentCardNumber;
}
My code so far:
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();
This works, but the string_agg function returns multiple repeated results, and more selection column I add the more I have to add in groupBy. Let's just say if I add a couple more classes and each class has 3 more field the query becomes really messy. Is there a way to generate this sql using JPA criteria API?
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;
As far as I can tell I can't join with subquery since JPA subquery doesn't allow multiple columns selection. Is there another way to do this?
To achieve the desired result without using subqueries in JPA Criteria API and without getting repeated results, you can use a combination of multiple joins and fetches along with a group by clause. Here's how you can modify your code to achieve that:
This approach avoids using subqueries and ensures that you don't get repeated results. It first fetches the necessary data and populates a map with student id and concatenated marks. Then, it constructs the
StudentInfoDTOobjects using the populated map.