我读了this article,了解如何使用复合键构建多对多。 实体是
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
}
现在,让我们假设,我想让学生拥有特定的courseId。
我的JPQ会是什么样子:
select s from Student s join fetch s.ratings r where r.id.courseId=:courserId
OR
select s from Student s join fetch s.ratings r where r.course.id=:courserId
否则会完全不同吗?
答案 0 :(得分:1)
您应该仔细检查这种JPA查询中的SQL,因为那里发生的事情可能比您想象的要多。
您在设置MetricReporter
与属性ManyToMany
的关系方面做得很出色,第二个查询可以正常工作,但很容易引起问题和可能的性能考虑。
为了只招收学生,正确的查询应该是
rating
给出日志
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()));
由于未预获取课程信息,JPA将生成一个额外的查询以获取课程信息。您可以自己预取即可解决该问题。
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)]
这给出了日志
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));
如果您正在创建REST服务,这也会影响JSON格式。在第一个实例中,您有一个带有一个条目的多个CourseRatings数组,在第二个实例中,您只具有了一个带有评分和学生条目的CourseRatings数组。基本上,只有部分学生列表(每个课程都有部分课程列表)不能准确表示数据库,而作为具有完整学生列表的课程。在这一点上,我不确定哪一种在SQL Server上效率更高,但是课程的数量应该比学生少得多,因此,如果您希望所有学生参加一门课程,则这样做可能会更好。
使用JPA,您应该检查SQL并仔细测试用例。由于有很多学生,每个学生有几门课程,因此性能或内存开销可能值得考虑。另请注意,如果您一开始没有在查询中使用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)
,结果可能会变得更加陌生。
答案 1 :(得分:0)
这是正确的版本:
SELECT s
FROM Student s
JOIN FETCH s.ratings r WHERE r.course.id = :courserId
您也可以将Student对象放在可嵌入ID的内部,以防不需要它的ID。这样一来,您将更容易阅读代码,并且不必将@Column(name = "student_id") Long studentId
设为不可插入且不可更新。