[英]JPQL: query entities with Many-to-Many relationship modeled using a composite key
class Student {
// ...
@OneToMany(mappedBy = "student")
Set<CourseRating> ratings;
// ...
}
class Course {
// ...
@OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
// ...
}
@Entity
class CourseRating {
@EmbeddedId
CourseRatingKey id;
@ManyToOne
@MapsId("student_id")
@JoinColumn(name = "student_id")
Student student;
@ManyToOne
@MapsId("course_id")
@JoinColumn(name = "course_id")
Course course;
int rating;
// standard constructors, getters, and setters
}
@Embeddable
class CourseRatingKey implements Serializable {
@Column(name = "student_id")
Long studentId;
@Column(name = "course_id")
Long courseId;
// standard constructors, getters, and setters
// hashcode and equals implementation
}
現在,讓我們假設,我想要獲得具有特定課程 ID 的學生。
那時我的 JPQ 會是什么樣子:
select s from Student s join fetch s.ratings r where r.id.courseId=:courserId
或者
select s from Student s join fetch s.ratings r where r.course.id=:courserId
或者它會完全不同?
您應該仔細檢查此類 JPA 查詢的 SQL,因為那里可能發生的事情比您意識到的要多。
您在設置與屬性rating
的ManyToMany
關系方面做得很好,您的第二個查詢將起作用,但很容易給您帶來問題和可能的性能考慮。
為了只獲得學生,正確的查詢可能是
em.createQuery("select s from Student s join fetch s.ratings r where r.course.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
這給了日志
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id where ratings1_.course_id=1
Hibernate: select course0_.id as id1_0_0_ from Course course0_ where course0_.id=?
Student(id=1):[CourseRating(id=model.CourseRatingKey@dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey@e10, student=Student(id=2), course=Course(id=1), rating=21)]
JPA 將生成一個額外的查詢來獲取課程信息,因為它沒有被預取。 您可以通過自己預取來解決這個問題。
em.createQuery("select s from Student s join fetch s.ratings r join fetch r.course c where c.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
這給了日志
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, course2_.id as id1_0_2_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id inner join Course course2_ on ratings1_.course_id=course2_.id where course2_.id=1
Student(id=1):[CourseRating(id=model.CourseRatingKey@dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey@e10, student=Student(id=2), course=Course(id=1), rating=21)]
存在的問題是您的學生只有部分課程評分。 如果您嘗試更新學生的評分,我認為您會導致其他課程評分丟失。 也許對您的用例來說不是問題,但您也可以通過學生列表獲取課程:
em.createQuery("select distinct c from Course c join fetch c.ratings r join fetch r.student where c.id = 1", Course.class).getSingleResult().getRatings().forEach(r->System.out.println(r.getStudent() + ":" + r));
這將通過稍微不同的查詢給出相同的結果,您可以更新學生的評分,而不會影響其他課程。
Hibernate: select distinct course0_.id as id1_0_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, student2_.id as id1_2_2_, ratings1_.rating as rating3_1_1_, ratings1_.course_id as course_i1_1_0__, ratings1_.student_id as student_2_1_0__ from Course course0_ inner join CourseRating ratings1_ on course0_.id=ratings1_.course_id inner join Student student2_ on ratings1_.student_id=student2_.id where course0_.id=1
Student(id=2):CourseRating(id=model.CourseRatingKey@e10, student=Student(id=2), course=Course(id=1), rating=21)
Student(id=1):CourseRating(id=model.CourseRatingKey@dd5, student=Student(id=1), course=Course(id=1), rating=11)
如果您正在創建 REST 服務,這也會影響 JSON 格式。 在第一種情況下,您有多個包含一個條目的 CourseRatings 數組,而在第二種情況下,您只有一個包含評分和學生條目的 CourseRatings 數組。 基本上,每個帶有部分課程列表的學生的部分列表並不是您的數據庫的准確表示,而作為具有完整學生列表的課程則是。 我現在不確定哪一個在 SQL 服務器上更有效,但是課程應該比學生少得多,所以如果你想讓所有學生都參加一個課程,那么這種方式可能會更好。
使用 JPA,您應該檢查 SQL 並仔細測試您的用例。 由於有很多學生,每個學生有幾門課程,因此性能或內存開銷可能值得考慮。 另請注意,如果您開始時沒有在查詢中使用fetch
,結果可能會變得更加奇怪。
這是正確的版本:
SELECT s
FROM Student s
JOIN FETCH s.ratings r WHERE r.course.id = :courserId
您也可以將 Student 對象放在可嵌入的 id 中,以防您不需要它的 id。 這樣,您的代碼會更容易閱讀,而且您不必將@Column(name = "student_id") Long studentId
設為不可插入和不可更新。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.