JPQL: запрашивать сущности с отношением «многие ко многим», смоделированные с использованием составного ключа - PullRequest
1 голос
/ 25 июня 2019

Я прочитал эту статью о том, как построить Many-To-Many, используя составной ключ.Объектами являются

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 голос
/ 26 июня 2019

Вам следует тщательно проверять SQL для такого рода запросов JPA, потому что там, вероятно, происходит больше, чем вы думаете.

Вы проделали большую работу по настройке отношения 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)

Это также может повлиять на форматирование JSON, если вы создаете службу REST. В первом случае у вас есть несколько массивов CourseRatings с одной записью, а во втором случае у вас есть только один массив CourseRatings с оценкой и записями студентов. По сути, неполный список Студентов, каждый из которых имеет частичный список Курсов, не является точным представлением вашей базы данных, в отличие от Курса с полным списком его Студентов. На данный момент я не уверен, какой из них более эффективен на сервере SQL, но курсов должно быть намного меньше, чем студентов, поэтому, если вы хотите, чтобы все студенты прошли курс, возможно, так будет лучше.

С JPA вы должны проверить SQL и тщательно протестировать ваши варианты использования. Так как на одного студента много студентов и несколько курсов, то стоит подумать о производительности или затратах памяти. Также обратите внимание, что результаты могут стать еще более странными, если вы не использовали fetch в своих запросах для начала.

0 голосов
/ 26 июня 2019

Это правильная версия:

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

Вы также можете поместить объект Student внутри вашего встраиваемого идентификатора, если вам не нужен его идентификатор. Таким образом, вам будет легче читать ваш код, и вам не нужно будет делать @Column(name = "student_id") Long studentId не вставляемым и не обновляемым.

...