You can use projections to achieve what you need.
Assuming that you are using the following entities:
@Entity
@Data
@AllArgsConstructor
public class Course implements Serializable {
@Id
@GeneratedValue
private Integer id;
private String name;
@Transient
private Long total;
public Course() {
}
public Course(String name) {
this.name = name;
}
}
@Entity
@Data
@AllArgsConstructor
public class Student implements Serializable {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToOne(optional = false)
private Course course;
public Student() {
}
public Student(String name, Course course) {
this.name = name;
this.course = course;
}
}
1) Then you can create the interface based projection
public interface CourseWithCountProjection {
Integer getId();
String getName();
Long getTotal();
}
and the following query method in the Course repository:
public interface CourseRepo extends JpaRepository<Course, Integer> {
@Query(value = "" +
"select " +
" c.id as id, " +
" c.name as name, " +
" count(s) as total " +
"from " +
" Course c " +
" left join Student s on s.course.id = c.id " +
"group by " +
" c " +
"order by " +
" count(s) desc" +
"", countQuery = "select count(c) from Course c")
Page<CourseWithCountProjection> getProjectionWithCount(Pageable pageable);
}
In this case you don't need the transient total
field in the Course
and you can remove it.
Note that you have to add the extra countQuery
parameter to the @Query
annotation because the main query has the grouping.
Also pay attention on aliases in the query (c.id as id
etc) - they are necessary when you are using projections.
2) Another way is to use the the Course
constructor in the JPQL query as @KarolDowbecki has already shown. You can use it with almost the same query:
public interface CourseRepo extends JpaRepository<Course, Integer> {
@Query(value = "" +
"select " +
" new Course(c.id, c.name, count(s)) " +
"from " +
" Course c " +
" left join Student s on s.course.id = c.id " +
"group by " +
" c " +
"order by " +
" count(s) desc" +
"", countQuery = "select count(c) from Course c")
Page<Course> getCoursesWithCount(Pageable pageable);
}
UPDATED
The first option is more preferable, because it divides the model (Course
) and the view (CourseWithCountProjection
) from each other.
UPDATED 2
To get dynamic sorting you can exclude order by
from the query and provide sorting in the Pageable
parameter of the query method, for example:
@Query(value = "" +
"select " +
" c.id as id, " +
" c.name as name, " +
" count(s) as total " +
"from " +
" Course c " +
" left join Student s on s.course.id = c.id " +
"group by " +
" c " +
"", countQuery = "select count(c) from Course c")
Page<CourseWithCountProjection> getProjectionWithCount(Pageable pageable);
Page<CourseWithCountProjection> result = parentRepo.getProjectionWithCount(PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "total")));
Working example is here: sb-jpa-orderby-related