有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java 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
}

现在,让我们假设,我想让学生有特定的课程

那么我的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

或者完全不同


共 (2) 个答案

  1. # 1 楼答案

    在这类JPA查询中,您应该仔细检查SQL,因为那里发生的事情可能比您意识到的要多

    您在设置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()));
    

    这就是日志

    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格式。在第一种情况下,你有一个条目的多个课程表数组,在第二种情况下,你只有一个评分和学生条目的课程表数组。基本上,部分学生列表和部分课程列表并不能准确地表示数据库,而作为一门完整的学生列表的课程,数据库是完整的。我现在不确定哪一个在SQL server上更有效,但课程应该比学生少很多,所以如果你想让所有学生都上一门课,这样可能更好

    使用JPA,您应该仔细检查SQL并测试用例。由于学生人数众多,每个学生都有几门课程,因此性能或内存开销可能值得考虑。还要注意的是,如果在查询开始时没有使用fetch,结果可能会变得更加奇怪

  2. # 2 楼答案

    This is the correct version:

    SELECT s 
    FROM Student s 
    JOIN FETCH s.ratings r WHERE r.course.id = :courserId
    

    你也可以把Student对象放在你的可嵌入id中,以防你不需要它的id。这样你就更容易阅读你的代码,而且你不必让@Column(name = "student_id") Long studentId成为不可插入和不可更新的