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

我的数据库模式:

问题图片:

这是 coursecourseRepository.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_studenttable中选出一个学生,这个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 来“弄清楚”的教训。