JPA findAll 按链表大小排序
JPA findAll order by a linked list size
我遇到了一个问题,我不知道是否可以使用 JPA 来解决。
我正在尝试使用 JPA 进行查询并进行以下操作:
获取所有具有课程实体字段(id、名称)以及非持久字段 (@Transient) 的课程,该字段将填充与该课程相关的所有学生人数
像这样:
List<Course> courses = courseRepository.findAll();
而是获取(为了示例目的,表示为 json)
[{1, soccer}, {2, art}, {3, singing}]
我需要这样的东西
[{1, soccer, 2}, {2, art, 0}, {3, singing, 1}]
如您所见,值 2、0 和 1 是所有相关行 table 学生的 count
Student table
| id | name | description | course |
| 1 | thg1 | a thing | 1 |
| 2 | thg2 | another one | 1 |
| 3 | thg3 | one more | 3 |
Course Table
| id | name |
| 1 | soccer |
| 2 | art |
| 3 | singing |
所以,限制是一个学生只能参加一门课程。
使用 JPA 我想 select 所有课程,但由于我正在使用分页,所以我无法在 spring 部分(我的意思是作为一项服务)做到这一点,我是试图直接用 JPA 做,有什么办法可以实现吗?也许有规范?想法?
谢谢
将字段标记为@Transient 会告诉 JPA 该字段未持久化。因此,JPA 不会将数据映射到该字段。
您只是希望该字段不可更新吗?也许您可以删除 @Transient 注释并将字段上的 'updateable' 属性 设置为 false?
您可以使用将创建投影的自定义 GROUP BY
查询:
@Query("SELECT new full.path.to.Course(c.id, c.name, count(s)) " +
"FROM Course c " +
"LEFT JOIN Student s " +
"GROUP BY c.id")
List<Course> findCourseWithStudentCount();
假设你在 Course
class 中有相应的构造函数,这将 return 投影对象。它们不会作为实体进行管理,但如果您不打算稍后修改它们,它们会这样做。
您可以在您的数据库中提供一个新视图,它收集您需要的所有信息(例如使用 group-by 语句)。然后视图应该包含这样的数据,完全按照您稍后需要的形式:
CourseWithStudents view
| id | course | students
| 1 | soccer | 2
| 2 | art | 0
| 3 | singing | 1
创建新的 class(可能扩展您当前的 Course
class)可以映射到此视图:
@Entity
@Table(name="CourseWithStudents")
class CourseWithStudents extends Course {
// Insert Mapping
}
然后您可以select新视图中的所有信息:
List<CourseWithStudents> courses = courseStudentRepository.findAll();
但是由于您无法更新视图,因此此解决方案仅在您只需要读取权限时才有效。如果您想更改 Course/Student 对象并在此 selection 之后在数据库中更新它,则此方法不起作用。
您可以使用projections来实现您所需要的。
假设您正在使用以下实体:
@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) 然后你可以创建 interface based projection
public interface CourseWithCountProjection {
Integer getId();
String getName();
Long getTotal();
}
以及课程库中的以下查询方法:
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);
}
在这种情况下,您不需要 Course
中的临时 total
字段,您可以 删除它。
请注意,您必须将额外的 countQuery
参数添加到 @Query
注释,因为主查询具有分组。
还要注意查询中的别名(c.id as id
等)- 使用投影时它们是必需的。
2) 另一种方法是在 JPQL 查询中使用 Course
构造函数,正如@KarolDowbecki 已经展示的那样。您可以将它与几乎相同的查询一起使用:
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);
}
已更新
第一个选项更可取,因为它将模型(Course
)和视图(CourseWithCountProjection
) 从彼此。
已更新 2
要获得动态排序,您可以从查询中排除 order by
,并在查询方法的 Pageable
参数中提供排序,例如:
@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")));
工作示例在这里:sb-jpa-orderby-related
您可以使用 Hibernate 中的 @Formula
注释:
@Formula("select count(*) FROM student s WHERE s.course = id")
private int totalStudents;
Sometimes, you want the Database to do some computation for you rather
than in the JVM, you might also create some kind of virtual column.
You can use a SQL fragment (aka formula) instead of mapping a property
into a column. This kind of property is read only (its value is
calculated by your formula fragment).
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
The SQL fragment can be as complex as you want and even include
subselects.
或者您可以使用双向关系来计算学生人数:
@OneToMany(mappedBy="course")
private List<Student> students;
public int getTotalStudents() {
return students.size();
}
或使用 transient
字段:
@OneToMany(mappedBy="course")
private List<Student> students;
@Transient
private int studentCount;
@PostLoad
public void setStudentCount() {
studentCount = students.size();
}
为避免 Cepr0 提到的 N+1 问题,您可以将获取模式设置为加入:
@OneToMany(mappedBy="course")
@Fetch(FetchMode.JOIN)
private List<Student> students;
我遇到了一个问题,我不知道是否可以使用 JPA 来解决。
我正在尝试使用 JPA 进行查询并进行以下操作:
获取所有具有课程实体字段(id、名称)以及非持久字段 (@Transient) 的课程,该字段将填充与该课程相关的所有学生人数
像这样:
List<Course> courses = courseRepository.findAll();
而是获取(为了示例目的,表示为 json)
[{1, soccer}, {2, art}, {3, singing}]
我需要这样的东西
[{1, soccer, 2}, {2, art, 0}, {3, singing, 1}]
如您所见,值 2、0 和 1 是所有相关行 table 学生的 count
Student table
| id | name | description | course |
| 1 | thg1 | a thing | 1 |
| 2 | thg2 | another one | 1 |
| 3 | thg3 | one more | 3 |
Course Table
| id | name |
| 1 | soccer |
| 2 | art |
| 3 | singing |
所以,限制是一个学生只能参加一门课程。
使用 JPA 我想 select 所有课程,但由于我正在使用分页,所以我无法在 spring 部分(我的意思是作为一项服务)做到这一点,我是试图直接用 JPA 做,有什么办法可以实现吗?也许有规范?想法?
谢谢
将字段标记为@Transient 会告诉 JPA 该字段未持久化。因此,JPA 不会将数据映射到该字段。
您只是希望该字段不可更新吗?也许您可以删除 @Transient 注释并将字段上的 'updateable' 属性 设置为 false?
您可以使用将创建投影的自定义 GROUP BY
查询:
@Query("SELECT new full.path.to.Course(c.id, c.name, count(s)) " +
"FROM Course c " +
"LEFT JOIN Student s " +
"GROUP BY c.id")
List<Course> findCourseWithStudentCount();
假设你在 Course
class 中有相应的构造函数,这将 return 投影对象。它们不会作为实体进行管理,但如果您不打算稍后修改它们,它们会这样做。
您可以在您的数据库中提供一个新视图,它收集您需要的所有信息(例如使用 group-by 语句)。然后视图应该包含这样的数据,完全按照您稍后需要的形式:
CourseWithStudents view
| id | course | students
| 1 | soccer | 2
| 2 | art | 0
| 3 | singing | 1
创建新的 class(可能扩展您当前的 Course
class)可以映射到此视图:
@Entity
@Table(name="CourseWithStudents")
class CourseWithStudents extends Course {
// Insert Mapping
}
然后您可以select新视图中的所有信息:
List<CourseWithStudents> courses = courseStudentRepository.findAll();
但是由于您无法更新视图,因此此解决方案仅在您只需要读取权限时才有效。如果您想更改 Course/Student 对象并在此 selection 之后在数据库中更新它,则此方法不起作用。
您可以使用projections来实现您所需要的。
假设您正在使用以下实体:
@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) 然后你可以创建 interface based projection
public interface CourseWithCountProjection {
Integer getId();
String getName();
Long getTotal();
}
以及课程库中的以下查询方法:
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);
}
在这种情况下,您不需要 Course
中的临时 total
字段,您可以 删除它。
请注意,您必须将额外的 countQuery
参数添加到 @Query
注释,因为主查询具有分组。
还要注意查询中的别名(c.id as id
等)- 使用投影时它们是必需的。
2) 另一种方法是在 JPQL 查询中使用 Course
构造函数,正如@KarolDowbecki 已经展示的那样。您可以将它与几乎相同的查询一起使用:
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);
}
已更新
第一个选项更可取,因为它将模型(Course
)和视图(CourseWithCountProjection
) 从彼此。
已更新 2
要获得动态排序,您可以从查询中排除 order by
,并在查询方法的 Pageable
参数中提供排序,例如:
@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")));
工作示例在这里:sb-jpa-orderby-related
您可以使用 Hibernate 中的 @Formula
注释:
@Formula("select count(*) FROM student s WHERE s.course = id")
private int totalStudents;
Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment).
@Formula("obj_length * obj_height * obj_width") public long getObjectVolume()
The SQL fragment can be as complex as you want and even include subselects.
或者您可以使用双向关系来计算学生人数:
@OneToMany(mappedBy="course")
private List<Student> students;
public int getTotalStudents() {
return students.size();
}
或使用 transient
字段:
@OneToMany(mappedBy="course")
private List<Student> students;
@Transient
private int studentCount;
@PostLoad
public void setStudentCount() {
studentCount = students.size();
}
为避免 Cepr0 提到的 N+1 问题,您可以将获取模式设置为加入:
@OneToMany(mappedBy="course")
@Fetch(FetchMode.JOIN)
private List<Student> students;