Spring 数据 JPA:有什么问题或如何更正此 jpql 查询
Spring Data JPA: What is wrong or how to correct this jpql query
实体关系是 -
一个学生属于一个学院,一个学院可以有多个学生。
所以在 Student --> College 之间存在 ManyToOne 关系,在 College --> Student 之间存在 OneToMany 关系。
实体如下。
@Entity
public class College {
@Id
@GeneratedValue
private int collegeId;
private String collegeName;
@OneToMany(targetEntity = Student.class, mappedBy = "college")
private List<Student> students;
和
@Entity
public class Student {
@Id
@GeneratedValue
private int studentId;
private String studentName;
@ManyToOne
@JoinColumn(name = "collegeId_fk")
private College college;
1) 我在 spring 数据 jpa 存储库中使用下面的 jpql 查询。
@Query("SELECT c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
public List<College> findByCollegeIdsJPQL();
我希望返回一个包含单个大学实体的列表,因为 collegeId 是大学实体的主键,我只为 IN
提供一个 ID。
但我得到的是所有具有相同主键 (collegeId=2) 的大学列表。返回列表的大小等于学院的学生人数。
System.err.println("collegeRepo.findByCollegeIdsJPQL().size(): " + collegeRepo.findByCollegeIdsJPQL().size());
//output: collegeRepo.findByCollegeIdsJPQL().size(): 6
而对于
collegeRepo.findByCollegeIdsJPQL().forEach( System.err::println );
输出:
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
2) 我注意到的另一个问题,
第二次调用 collegeRepo.findByCollegeIdsJPQL()
会导致另一个 sql 查询,即数据库命中。这不能从会话(第一级)缓存中提供。我已经用
注释了调用方法
@Transactional
public void run(String... args) throws Exception {
输出日志-
2022-02-20 15:10:59.140[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_0__] : [INTEGER]) - [6]
collegeRepo.findByCollegeIdsJPQL().size(): 6
[2m2022-02-20 15:10:59.153[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.t.i.TransactionInterceptor [0;39m [2m:[0;39m No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByCollegeIdsJPQL]: This method is not transactional.
Hibernate:
select
college0_.college_id as college_1_1_0_,
students1_.student_id as student_1_12_1_,
college0_.college_name as college_2_1_0_,
students1_.college_id_fk as college_3_12_1_,
students1_.student_name as student_2_12_1_,
students1_.college_id_fk as college_3_12_0__,
students1_.student_id as student_1_12_0__
from
college college0_
left outer join
student students1_
on college0_.college_id=students1_.college_id_fk
where
college0_.college_id in (
2
)
[2m2022-02-20 15:10:59.245[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.a.c.c.C.[Tomcat].[localhost].[/] [0;39m [2m:[0;39m Initializing Spring DispatcherServlet 'dispatcherServlet'
[2m2022-02-20 15:10:59.246[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Initializing Servlet 'dispatcherServlet'
[2m2022-02-20 15:10:59.247[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Completed initialization in 1 ms
[2m2022-02-20 15:10:59.403[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [1]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [3]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [4]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [5]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [6]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
[2m2022-02-20 15:10:59.407[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.t.i.TransactionInterceptor [0;39m [2m:[0;39m Completing transaction for [com.demo.MyRunner.run]
(1) 因为 LEFT JOIN
将 return 学院及其学生的组合。因此,如果一所大学有 N 名学生,则将 return 编辑 N 条记录。您必须添加 DISTINCT
才能删除重复项:
@Query("SELECT distinct c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
public List<College> findByCollegeIdsJPQL();
但是,它会导致生成的 SQL 有 distinct
关键字,这可能会影响性能。因此,从 Hibernate 5.2 开始,他们提供了一个名为 hibernate.query.passDistinctThrough
的查询提示,它可以配置为不在生成的 SQL 中添加 distinct
关键字,Hibernate 将帮助删除重复项:
@Query("SELECT distinct c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
@QueryHints(@QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false"))
public List<College> findByCollegeIdsJPQL();
详情请看这里blog post。
(2) 正常。您必须配置二级缓存和查询缓存以防止再次命中数据库。一级缓存主要在同一事务中使用 EntityManager#get()
通过 ID 获取实体时起作用。它在使用 JPQL 查询时不起作用。
实体关系是 - 一个学生属于一个学院,一个学院可以有多个学生。 所以在 Student --> College 之间存在 ManyToOne 关系,在 College --> Student 之间存在 OneToMany 关系。
实体如下。
@Entity
public class College {
@Id
@GeneratedValue
private int collegeId;
private String collegeName;
@OneToMany(targetEntity = Student.class, mappedBy = "college")
private List<Student> students;
和
@Entity
public class Student {
@Id
@GeneratedValue
private int studentId;
private String studentName;
@ManyToOne
@JoinColumn(name = "collegeId_fk")
private College college;
1) 我在 spring 数据 jpa 存储库中使用下面的 jpql 查询。
@Query("SELECT c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
public List<College> findByCollegeIdsJPQL();
我希望返回一个包含单个大学实体的列表,因为 collegeId 是大学实体的主键,我只为 IN
提供一个 ID。
但我得到的是所有具有相同主键 (collegeId=2) 的大学列表。返回列表的大小等于学院的学生人数。
System.err.println("collegeRepo.findByCollegeIdsJPQL().size(): " + collegeRepo.findByCollegeIdsJPQL().size());
//output: collegeRepo.findByCollegeIdsJPQL().size(): 6
而对于
collegeRepo.findByCollegeIdsJPQL().forEach( System.err::println );
输出:
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
大学 [collegeId=2, collegeName=college2]
2) 我注意到的另一个问题,
第二次调用 collegeRepo.findByCollegeIdsJPQL()
会导致另一个 sql 查询,即数据库命中。这不能从会话(第一级)缓存中提供。我已经用
@Transactional
public void run(String... args) throws Exception {
输出日志-
2022-02-20 15:10:59.140[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_0__] : [INTEGER]) - [6]
collegeRepo.findByCollegeIdsJPQL().size(): 6
[2m2022-02-20 15:10:59.153[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.t.i.TransactionInterceptor [0;39m [2m:[0;39m No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByCollegeIdsJPQL]: This method is not transactional.
Hibernate:
select
college0_.college_id as college_1_1_0_,
students1_.student_id as student_1_12_1_,
college0_.college_name as college_2_1_0_,
students1_.college_id_fk as college_3_12_1_,
students1_.student_name as student_2_12_1_,
students1_.college_id_fk as college_3_12_0__,
students1_.student_id as student_1_12_0__
from
college college0_
left outer join
student students1_
on college0_.college_id=students1_.college_id_fk
where
college0_.college_id in (
2
)
[2m2022-02-20 15:10:59.245[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.a.c.c.C.[Tomcat].[localhost].[/] [0;39m [2m:[0;39m Initializing Spring DispatcherServlet 'dispatcherServlet'
[2m2022-02-20 15:10:59.246[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Initializing Servlet 'dispatcherServlet'
[2m2022-02-20 15:10:59.247[0;39m [32m INFO[0;39m [35m18416[0;39m [2m---[0;39m [2m[on(3)-127.0.0.1][0;39m [36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Completed initialization in 1 ms
[2m2022-02-20 15:10:59.403[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [1]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.404[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [3]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.405[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [4]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [5]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_1_1_0_] : [INTEGER]) - [2]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([student_1_12_1_] : [INTEGER]) - [6]
[2m2022-02-20 15:10:59.406[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.h.type.descriptor.sql.BasicExtractor [0;39m [2m:[0;39m extracted value ([college_3_12_0__] : [INTEGER]) - [2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
College [collegeId=2, collegeName=college2]
[2m2022-02-20 15:10:59.407[0;39m [32mTRACE[0;39m [35m18416[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.t.i.TransactionInterceptor [0;39m [2m:[0;39m Completing transaction for [com.demo.MyRunner.run]
(1) 因为 LEFT JOIN
将 return 学院及其学生的组合。因此,如果一所大学有 N 名学生,则将 return 编辑 N 条记录。您必须添加 DISTINCT
才能删除重复项:
@Query("SELECT distinct c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
public List<College> findByCollegeIdsJPQL();
但是,它会导致生成的 SQL 有 distinct
关键字,这可能会影响性能。因此,从 Hibernate 5.2 开始,他们提供了一个名为 hibernate.query.passDistinctThrough
的查询提示,它可以配置为不在生成的 SQL 中添加 distinct
关键字,Hibernate 将帮助删除重复项:
@Query("SELECT distinct c FROM College c LEFT JOIN FETCH c.students where c.collegeId IN (2)")
@QueryHints(@QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false"))
public List<College> findByCollegeIdsJPQL();
详情请看这里blog post。
(2) 正常。您必须配置二级缓存和查询缓存以防止再次命中数据库。一级缓存主要在同一事务中使用 EntityManager#get()
通过 ID 获取实体时起作用。它在使用 JPQL 查询时不起作用。