JPQL:使用复合键对具有多对多关系的实体进行查询

时间:2019-06-25 19:00:23

标签: java hibernate jpa spring-data-jpa jpql

我读了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

否则会完全不同吗?

2 个答案:

答案 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设为不可插入且不可更新。