Spring 数据 JPA 查询方法 returns 当数据不在数据库中时加倍
Spring Data JPA Query Method returns double data when it's not present in the database
我有一个网络应用程序,用于存储有关学生、教师、课程和成绩的信息。当 adding/editing 学生评分时,我 运行 遇到了问题。出于某种原因,JPA 存储库内置的 findById()
方法正在 return 一门双倍学生的课程(即使在数据库中 class 中显然只有一个学生)。我附上了我的模型和方法的图像以及我尝试调试问题。
这是调用方法的地方,我的 listStudentsByCourse
方法只是 return 学生列表:
这里是 listStudentsByCourse
方法体:
这是我的模型:
学生:
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Student {
@Id
private String username;
private String password;
private String name;
private String surname;
@OneToMany(mappedBy = "student", fetch = FetchType.EAGER)
private List<Grade> grades;
public Student(String username, String password, String name, String surname) {
this.username = username;
this.password = password;
this.name = name;
this.surname = surname;
this.grades = new ArrayList<>();
}
public Character getGradeForCourse (Course c) {
return this.grades.stream()
.filter(i -> i.getCourse().getCourseId().equals(c.getCourseId()))
.findFirst().orElse(new Grade()).getGrade();
}
}
课程:
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long courseId;
private String name;
private String description;
@ManyToOne
private Teacher teacher;
@ManyToMany(fetch = FetchType.EAGER)
private List<Student> students;
@Enumerated(value = EnumType.STRING)
private Type type;
public Course(String name, String description, Teacher teacher, Type type) {
this.name = name;
this.description = description;
this.teacher = teacher;
this.type = type;
this.students = new ArrayList<>();
}
}
等级:
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Grade {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Character grade;
@ManyToOne
private Course course;
@ManyToOne
private Student student;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime timestamp;
public Grade(Character grade, Course course, Student student, LocalDateTime timestamp) {
this.grade = grade;
this.course = course;
this.student = student;
this.timestamp = timestamp;
}
}
我的数据库模式:
问题图片:
这是 course
由 courseRepository.findById()
编辑的 return
我在另一门课程中添加了同一名学生,我想知道他们是否以某种方式混淆了,但这不应该是可能的,因为课程不同(ID 也不同)。
这个问题真是让我很为难,我真的不明白这是怎么回事。任何信息都会有帮助,谢谢!
编辑 1:
我阅读了所有评论并将它们添加到我的代码中,但问题仍然存在。我现在注意到,当我有一个已经在课程中取得成绩的学生时,数据会翻倍,如果我添加另一个没有成绩的新学生,数据不仅在前端而且在数据库中都会翻倍。
这是我在现有课程中添加新学生的方法,代码有什么问题吗?:
@Override
public Course addStudentInCourse(String username, Long courseId) {
if (this.studentRepository.findByUsername(username).isEmpty()) {
throw new StudentNotFoundException();
}
if (this.courseRepository.findById(courseId).isEmpty()) {
throw new CourseNotFoundException();
}
Student s = this.studentRepository.findByUsername(username).get();
Course c = this.courseRepository.findById(courseId).get();
if (!c.getStudents().contains(s)){
c.getStudents().add(s);
return this.courseRepository.save(c);
} else {
return c;
}
}
编辑 2:
我已经取得了突破,有点。
首先,这是我的照片 course_student
table:
我意识到 courseService.findById(courseId)
,如果我在不同课程中有同一名学生,它也会检索该学生(当它不应该检索时)。例如,我在课程 5 和课程 7 中有 markos
。
如果我使用以下查询方法 return 某个课程的学生列表,它会正确检索学生。
studentRepository.findAllByCourses(c);
这是我调试的图片:
我应该注意到 Student
的课程是延迟加载的。
为什么查询方法 findById
returning a Course
与该课程无关的学生?
编辑 3: 所有 table 和代码与编辑 2 中的相同(我只是不断刷新获取数据的页面)。我按照你们许多人的建议记录了 SQL。以下 SQL 查询来自 courseService.findById(courseId)
方法。我理解第一部分,它从 Course
table 中获取具有正确 ID (7) 的课程。然后从id为5的course_student
table中选出一个学生,这个5是哪里来的?
Hibernate:
select
course0_.course_id as course_i1_0_0_,
course0_.description as descript2_0_0_,
course0_.name as name3_0_0_,
course0_.teacher_id as teacher_5_0_0_,
course0_.type as type4_0_0_,
students1_.fk_course as fk_cours1_1_1_,
student2_.username as fk_stude2_1_1_,
student2_.username as username1_3_2_,
student2_.name as name2_3_2_,
student2_.password as password3_3_2_,
student2_.surname as surname4_3_2_,
grades3_.fk_student as fk_stude5_2_3_,
grades3_.id as id1_2_3_,
grades3_.id as id1_2_4_,
grades3_.fk_course as fk_cours4_2_4_,
grades3_.grade as grade2_2_4_,
grades3_.fk_student as fk_stude5_2_4_,
grades3_.timestamp as timestam3_2_4_,
course4_.course_id as course_i1_0_5_,
course4_.description as descript2_0_5_,
course4_.name as name3_0_5_,
course4_.teacher_id as teacher_5_0_5_,
course4_.type as type4_0_5_,
teacher5_.id as id1_4_6_,
teacher5_.date_of_employment as date_of_2_4_6_,
teacher5_.name as name3_4_6_,
teacher5_.surname as surname4_4_6_
from
course course0_
left outer join
course_student students1_
on course0_.course_id=students1_.fk_course
left outer join
student student2_
on students1_.fk_student=student2_.username
left outer join
grade grades3_
on student2_.username=grades3_.fk_student
left outer join
course course4_
on grades3_.fk_course=course4_.course_id
left outer join
teacher teacher5_
on course4_.teacher_id=teacher5_.id
where
course0_.course_id=?
2022-01-06 00:56:38.869 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [7]
Hibernate:
select
students0_.fk_course as fk_cours1_1_0_,
students0_.fk_student as fk_stude2_1_0_,
student1_.username as username1_3_1_,
student1_.name as name2_3_1_,
student1_.password as password3_3_1_,
student1_.surname as surname4_3_1_
from
course_student students0_
inner join
student student1_
on students0_.fk_student=student1_.username
where
students0_.fk_course=?
2022-01-06 00:56:38.881 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [5]
Hibernate:
select
course0_.course_id as course_i1_0_0_,
course0_.description as descript2_0_0_,
course0_.name as name3_0_0_,
course0_.teacher_id as teacher_5_0_0_,
course0_.type as type4_0_0_,
students1_.fk_course as fk_cours1_1_1_,
student2_.username as fk_stude2_1_1_,
student2_.username as username1_3_2_,
student2_.name as name2_3_2_,
student2_.password as password3_3_2_,
student2_.surname as surname4_3_2_,
grades3_.fk_student as fk_stude5_2_3_,
grades3_.id as id1_2_3_,
grades3_.id as id1_2_4_,
grades3_.fk_course as fk_cours4_2_4_,
grades3_.grade as grade2_2_4_,
grades3_.fk_student as fk_stude5_2_4_,
grades3_.timestamp as timestam3_2_4_,
course4_.course_id as course_i1_0_5_,
course4_.description as descript2_0_5_,
course4_.name as name3_0_5_,
course4_.teacher_id as teacher_5_0_5_,
course4_.type as type4_0_5_,
teacher5_.id as id1_4_6_,
teacher5_.date_of_employment as date_of_2_4_6_,
teacher5_.name as name3_4_6_,
teacher5_.surname as surname4_4_6_
from
course course0_
left outer join
course_student students1_
on course0_.course_id=students1_.fk_course
left outer join
student student2_
on students1_.fk_student=student2_.username
left outer join
grade grades3_
on student2_.username=grades3_.fk_student
left outer join
course course4_
on grades3_.fk_course=course4_.course_id
left outer join
teacher teacher5_
on course4_.teacher_id=teacher5_.id
where
course0_.course_id=?
2022-01-06 00:56:38.893 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [7]
Hibernate:
select
students0_.fk_course as fk_cours1_1_0_,
students0_.fk_student as fk_stude2_1_0_,
student1_.username as username1_3_1_,
student1_.name as name2_3_1_,
student1_.password as password3_3_1_,
student1_.surname as surname4_3_1_
from
course_student students0_
inner join
student student1_
on students0_.fk_student=student1_.username
where
students0_.fk_course=?
2022-01-06 00:56:38.909 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [5]
您的实体课程与实体学生的关系是多对多的。这意味着具有相同 ID 的课程行可以容纳很多学生。我怀疑当您调用 findById()
时,生成的 SQL 并没有明显地 select 给定 courseId 的学生。
您的架构中有一个问题,即 course_students
尚未在您的代码中实现。这是一个尝试建议,在您的课程实体中添加 @JoinTable(...)
:
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long course_id;
.
.
// your course fields
.
.
/*** Here you add the many to many table ***/
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "course_students",
joinColumns = {
@JoinColumn(name = "course_course_id", referencedColumnName = "course_id")},
inverseJoinColumns = {
@JoinColumn(name = "students_username", referencedColumnName = "username")})
private List<Student> students;
... //more fields
}
然后在您的 Student 实体中,从联接 table:
中指定您的映射属性
@Entity
public class Student {
@Id
private String username;
.. // student fields
@ManyToMany(mappedBy = "students")
List<Course> courses;
.. //more..
}
谢谢大家的建议,我已经弄明白了。
这是我一直以来绘制人际关系的方式。当我将 Grade
实体中的课程映射为 ManyToOne 时,我忘记了使用 OneToMany 注释映射 Course
实体中的成绩。这导致 Hibernate 变得困惑,并从两个表中为课程获取同一个学生。
这是一个始终正确映射您的关系并且不要依赖 Hibernate 来“弄清楚”的教训。
我有一个网络应用程序,用于存储有关学生、教师、课程和成绩的信息。当 adding/editing 学生评分时,我 运行 遇到了问题。出于某种原因,JPA 存储库内置的 findById()
方法正在 return 一门双倍学生的课程(即使在数据库中 class 中显然只有一个学生)。我附上了我的模型和方法的图像以及我尝试调试问题。
这是调用方法的地方,我的 listStudentsByCourse
方法只是 return 学生列表:
这里是 listStudentsByCourse
方法体:
这是我的模型:
学生:
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Student {
@Id
private String username;
private String password;
private String name;
private String surname;
@OneToMany(mappedBy = "student", fetch = FetchType.EAGER)
private List<Grade> grades;
public Student(String username, String password, String name, String surname) {
this.username = username;
this.password = password;
this.name = name;
this.surname = surname;
this.grades = new ArrayList<>();
}
public Character getGradeForCourse (Course c) {
return this.grades.stream()
.filter(i -> i.getCourse().getCourseId().equals(c.getCourseId()))
.findFirst().orElse(new Grade()).getGrade();
}
}
课程:
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long courseId;
private String name;
private String description;
@ManyToOne
private Teacher teacher;
@ManyToMany(fetch = FetchType.EAGER)
private List<Student> students;
@Enumerated(value = EnumType.STRING)
private Type type;
public Course(String name, String description, Teacher teacher, Type type) {
this.name = name;
this.description = description;
this.teacher = teacher;
this.type = type;
this.students = new ArrayList<>();
}
}
等级:
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Grade {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Character grade;
@ManyToOne
private Course course;
@ManyToOne
private Student student;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime timestamp;
public Grade(Character grade, Course course, Student student, LocalDateTime timestamp) {
this.grade = grade;
this.course = course;
this.student = student;
this.timestamp = timestamp;
}
}
我的数据库模式:
问题图片:
这是 course
由 courseRepository.findById()
我在另一门课程中添加了同一名学生,我想知道他们是否以某种方式混淆了,但这不应该是可能的,因为课程不同(ID 也不同)。
这个问题真是让我很为难,我真的不明白这是怎么回事。任何信息都会有帮助,谢谢!
编辑 1: 我阅读了所有评论并将它们添加到我的代码中,但问题仍然存在。我现在注意到,当我有一个已经在课程中取得成绩的学生时,数据会翻倍,如果我添加另一个没有成绩的新学生,数据不仅在前端而且在数据库中都会翻倍。
这是我在现有课程中添加新学生的方法,代码有什么问题吗?:
@Override
public Course addStudentInCourse(String username, Long courseId) {
if (this.studentRepository.findByUsername(username).isEmpty()) {
throw new StudentNotFoundException();
}
if (this.courseRepository.findById(courseId).isEmpty()) {
throw new CourseNotFoundException();
}
Student s = this.studentRepository.findByUsername(username).get();
Course c = this.courseRepository.findById(courseId).get();
if (!c.getStudents().contains(s)){
c.getStudents().add(s);
return this.courseRepository.save(c);
} else {
return c;
}
}
编辑 2: 我已经取得了突破,有点。
首先,这是我的照片 course_student
table:
我意识到 courseService.findById(courseId)
,如果我在不同课程中有同一名学生,它也会检索该学生(当它不应该检索时)。例如,我在课程 5 和课程 7 中有 markos
。
如果我使用以下查询方法 return 某个课程的学生列表,它会正确检索学生。
studentRepository.findAllByCourses(c);
这是我调试的图片:
我应该注意到 Student
的课程是延迟加载的。
为什么查询方法 findById
returning a Course
与该课程无关的学生?
编辑 3: 所有 table 和代码与编辑 2 中的相同(我只是不断刷新获取数据的页面)。我按照你们许多人的建议记录了 SQL。以下 SQL 查询来自 courseService.findById(courseId)
方法。我理解第一部分,它从 Course
table 中获取具有正确 ID (7) 的课程。然后从id为5的course_student
table中选出一个学生,这个5是哪里来的?
Hibernate:
select
course0_.course_id as course_i1_0_0_,
course0_.description as descript2_0_0_,
course0_.name as name3_0_0_,
course0_.teacher_id as teacher_5_0_0_,
course0_.type as type4_0_0_,
students1_.fk_course as fk_cours1_1_1_,
student2_.username as fk_stude2_1_1_,
student2_.username as username1_3_2_,
student2_.name as name2_3_2_,
student2_.password as password3_3_2_,
student2_.surname as surname4_3_2_,
grades3_.fk_student as fk_stude5_2_3_,
grades3_.id as id1_2_3_,
grades3_.id as id1_2_4_,
grades3_.fk_course as fk_cours4_2_4_,
grades3_.grade as grade2_2_4_,
grades3_.fk_student as fk_stude5_2_4_,
grades3_.timestamp as timestam3_2_4_,
course4_.course_id as course_i1_0_5_,
course4_.description as descript2_0_5_,
course4_.name as name3_0_5_,
course4_.teacher_id as teacher_5_0_5_,
course4_.type as type4_0_5_,
teacher5_.id as id1_4_6_,
teacher5_.date_of_employment as date_of_2_4_6_,
teacher5_.name as name3_4_6_,
teacher5_.surname as surname4_4_6_
from
course course0_
left outer join
course_student students1_
on course0_.course_id=students1_.fk_course
left outer join
student student2_
on students1_.fk_student=student2_.username
left outer join
grade grades3_
on student2_.username=grades3_.fk_student
left outer join
course course4_
on grades3_.fk_course=course4_.course_id
left outer join
teacher teacher5_
on course4_.teacher_id=teacher5_.id
where
course0_.course_id=?
2022-01-06 00:56:38.869 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [7]
Hibernate:
select
students0_.fk_course as fk_cours1_1_0_,
students0_.fk_student as fk_stude2_1_0_,
student1_.username as username1_3_1_,
student1_.name as name2_3_1_,
student1_.password as password3_3_1_,
student1_.surname as surname4_3_1_
from
course_student students0_
inner join
student student1_
on students0_.fk_student=student1_.username
where
students0_.fk_course=?
2022-01-06 00:56:38.881 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [5]
Hibernate:
select
course0_.course_id as course_i1_0_0_,
course0_.description as descript2_0_0_,
course0_.name as name3_0_0_,
course0_.teacher_id as teacher_5_0_0_,
course0_.type as type4_0_0_,
students1_.fk_course as fk_cours1_1_1_,
student2_.username as fk_stude2_1_1_,
student2_.username as username1_3_2_,
student2_.name as name2_3_2_,
student2_.password as password3_3_2_,
student2_.surname as surname4_3_2_,
grades3_.fk_student as fk_stude5_2_3_,
grades3_.id as id1_2_3_,
grades3_.id as id1_2_4_,
grades3_.fk_course as fk_cours4_2_4_,
grades3_.grade as grade2_2_4_,
grades3_.fk_student as fk_stude5_2_4_,
grades3_.timestamp as timestam3_2_4_,
course4_.course_id as course_i1_0_5_,
course4_.description as descript2_0_5_,
course4_.name as name3_0_5_,
course4_.teacher_id as teacher_5_0_5_,
course4_.type as type4_0_5_,
teacher5_.id as id1_4_6_,
teacher5_.date_of_employment as date_of_2_4_6_,
teacher5_.name as name3_4_6_,
teacher5_.surname as surname4_4_6_
from
course course0_
left outer join
course_student students1_
on course0_.course_id=students1_.fk_course
left outer join
student student2_
on students1_.fk_student=student2_.username
left outer join
grade grades3_
on student2_.username=grades3_.fk_student
left outer join
course course4_
on grades3_.fk_course=course4_.course_id
left outer join
teacher teacher5_
on course4_.teacher_id=teacher5_.id
where
course0_.course_id=?
2022-01-06 00:56:38.893 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [7]
Hibernate:
select
students0_.fk_course as fk_cours1_1_0_,
students0_.fk_student as fk_stude2_1_0_,
student1_.username as username1_3_1_,
student1_.name as name2_3_1_,
student1_.password as password3_3_1_,
student1_.surname as surname4_3_1_
from
course_student students0_
inner join
student student1_
on students0_.fk_student=student1_.username
where
students0_.fk_course=?
2022-01-06 00:56:38.909 TRACE 15804 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [5]
您的实体课程与实体学生的关系是多对多的。这意味着具有相同 ID 的课程行可以容纳很多学生。我怀疑当您调用 findById()
时,生成的 SQL 并没有明显地 select 给定 courseId 的学生。
您的架构中有一个问题,即 course_students
尚未在您的代码中实现。这是一个尝试建议,在您的课程实体中添加 @JoinTable(...)
:
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long course_id;
.
.
// your course fields
.
.
/*** Here you add the many to many table ***/
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "course_students",
joinColumns = {
@JoinColumn(name = "course_course_id", referencedColumnName = "course_id")},
inverseJoinColumns = {
@JoinColumn(name = "students_username", referencedColumnName = "username")})
private List<Student> students;
... //more fields
}
然后在您的 Student 实体中,从联接 table:
中指定您的映射属性@Entity
public class Student {
@Id
private String username;
.. // student fields
@ManyToMany(mappedBy = "students")
List<Course> courses;
.. //more..
}
谢谢大家的建议,我已经弄明白了。
这是我一直以来绘制人际关系的方式。当我将 Grade
实体中的课程映射为 ManyToOne 时,我忘记了使用 OneToMany 注释映射 Course
实体中的成绩。这导致 Hibernate 变得困惑,并从两个表中为课程获取同一个学生。
这是一个始终正确映射您的关系并且不要依赖 Hibernate 来“弄清楚”的教训。