如何用Spring Data JPA @ManyToMany关联3表展示学生科目成绩
问题:关联Student、Subject、StudentMark表返回嵌套格式数据
我需要展示学生特定科目的成绩,希望将student、subject、student_mark三张表关联整合,返回如下格式的嵌套数据:
"studentList": [ { "id": "2", "username": "IN", "password": "admin", "fullName": "Depp", "email": "i@live.com", "subjectList": [ { "id": 1, "name": "Programim 1", "markList": [ { "mark": 10, "description": "Excellent" } ] }, { "id": 2, "name": "Programim 2", "markList": [ { "mark": 9, "description": "Very Good" } ] }, { "id": 3, "name": "Calculus", "markList": [ { "mark": 8, "description": "Good" } ] }, { "id": 4, "name": "Discrete mathematics", "markList": [ { "mark": 7, "description": "Well" } ] } ] } ]
数据库架构说明
数据库包含三张核心表及关联表:
Student:存储学生基础信息,主键为user_idSubject:存储科目信息,主键为idStudentMark:存储学生科目成绩,包含student_id(关联Student.user_id)、subject_id(关联Subject.id)、mark、description字段,主键为grade_idstudent_subject:维护学生与科目的选修关系,包含student_id和subject_id
当前实体模型
Student模型
package springuniversity.Model; import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.*; import java.util.List; @Entity @Table(name="Student") public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="user_id") @JsonProperty private Integer user_id; @Column(name="username") @JsonProperty private String username; @Column(name="password") private String password; @Column(name="fullname") @JsonProperty private String fullName; @Column(name="email") @JsonProperty private String email; @ManyToMany @JoinTable(name = "student_subject", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "user_id"), inverseJoinColumns = @JoinColumn(name = "subject_id", referencedColumnName = "id")) @JsonProperty private List<Subject> subjectList; public Student() {} public Student(int id, String username, String password, String fullName, String email, List<Subject> subjectList) { super(); this.user_id = id; this.username = username; this.password = password; this.fullName = fullName; this.email = email; this.subjectList=subjectList; } public String getId() { return String.valueOf(user_id); } public void setId(int user_id) { this.user_id = user_id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<Subject> getSubjectList() { return subjectList; } public void setSubjectList(List<Subject> subjectList) { this.subjectList = subjectList; } }
Subject模型
package springuniversity.Model; import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.*; import java.util.List; @Entity @Table(name="Subject") public class Subject { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="id") @JsonProperty private Integer id; @Column(name="name") private String name; @ManyToMany @JoinTable(name = "student_subject_marks", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "user_id"), inverseJoinColumns = @JoinColumn(name = "subject_id", referencedColumnName = "id")) @JsonProperty private List<Subject> markList; public Subject() { this.id = id; this.name=name; } public void setId(int id) { this.id=id; } public int getId() { return id; } public void setName(String name) { this.name=name; } public String getName() { return name; } public List<Subject> getMarkList() { return markList; } public void setMarkList(List<Subject> markList) { this.markList = markList; } }
StudentMark模型
package springuniversity.Model; import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.*; @Entity @Table(name="StudentMark") public class StudentMark { @Id private int grade_id; @Column(name="mark") private int mark; @Column(name="description") private String description; public int getGrade_id() { return grade_id; } public void setGrade_id(int grade_id) { this.grade_id = grade_id; } public int getMark() { return mark; } public void setMark(int mark) { this.mark = mark; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
当前问题
我尝试在Subject模型中创建了markList,但不知道如何通过Spring Data JPA或HQL正确映射三个表的关联关系,目前仅能展示学生所选科目,无法关联出对应的成绩数据。
解决方案
1. 修正实体类关联关系
核心错误在于Subject的markList类型错误,且StudentMark未关联Student和Subject,调整如下:
修正后的Subject模型
将markList改为List<StudentMark>,通过一对多关联该科目下的所有成绩:
package springuniversity.Model; import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.*; import java.util.List; @Entity @Table(name="Subject") public class Subject { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="id") @JsonProperty private Integer id; @Column(name="name") @JsonProperty private String name; // 关联该科目下的所有学生成绩 @OneToMany(mappedBy = "subject") @JsonProperty private List<StudentMark> markList; public Subject() {} public void setId(int id) { this.id=id; } public int getId() { return id; } public void setName(String name) { this.name=name; } public String getName() { return name; } public List<StudentMark> getMarkList() { return markList; } public void setMarkList(List<StudentMark> markList) { this.markList = markList; } }
修正后的StudentMark模型
添加对Student和Subject的外键关联,明确映射关系:
package springuniversity.Model; import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.*; @Entity @Table(name="StudentMark") public class StudentMark { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="grade_id") private int grade_id; @Column(name="mark") @JsonProperty private int mark; @Column(name="description") @JsonProperty private String description; // 关联学生 @ManyToOne @JoinColumn(name = "student_id", referencedColumnName = "user_id") private Student student; // 关联科目 @ManyToOne @JoinColumn(name = "subject_id", referencedColumnName = "id") private Subject subject; public int getGrade_id() { return grade_id; } public void setGrade_id(int grade_id) { this.grade_id = grade_id; } public int getMark() { return mark; } public void setMark(int mark) { this.mark = mark; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } }
2. 使用Spring Data JPA查询数据
方法1:HQL关联查询
创建StudentRepository,编写HQL查询加载学生、科目及成绩:
package springuniversity.Repository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import springuniversity.Model.Student; import java.util.List; public interface StudentRepository extends CrudRepository<Student, Integer> { @Query("SELECT s FROM Student s JOIN FETCH s.subjectList sub JOIN FETCH sub.markList sm WHERE sm.student = s") List<Student> findAllWithSubjectsAndMarks(); @Query("SELECT s FROM Student s JOIN FETCH s.subjectList sub JOIN FETCH sub.markList sm WHERE s.user_id = :studentId AND sm.student = s") Student findByIdWithSubjectsAndMarks(Integer studentId); }
方法2:EntityGraph简化加载
用@EntityGraph指定关联属性,避免N+1查询:
@EntityGraph(attributePaths = {"subjectList", "subjectList.markList"}) List<Student> findAll(); @EntityGraph(attributePaths = {"subjectList", "subjectList.markList"}) Student findByUser_id(Integer studentId);
3. 处理JSON循环引用
双向关联会导致序列化循环,添加Jackson注解解决:
// Student类中 @ManyToMany @JoinTable(name = "student_subject", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "user_id"), inverseJoinColumns = @JoinColumn(name = "subject_id", referencedColumnName = "id")) @JsonProperty @JsonManagedReference private List<Subject> subjectList; // Subject类中 @OneToMany(mappedBy = "subject") @JsonProperty @JsonManagedReference private List<StudentMark> markList; // StudentMark类中 @ManyToOne @JoinColumn(name = "student_id", referencedColumnName = "user_id") @JsonBackReference private Student student; @ManyToOne @JoinColumn(name = "subject_id", referencedColumnName = "id") @JsonBackReference private Subject subject;
4. 控制器返回数据
在Controller中调用Repository方法返回格式化数据:
package springuniversity.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import springuniversity.Model.Student; import springuniversity.Repository.StudentRepository; import java.util.List; @RestController public class StudentController { private final StudentRepository studentRepository; public StudentController(StudentRepository studentRepository) { this.studentRepository = studentRepository; } @GetMapping("/students") public List<Student> getAllStudentsWithMarks() { return studentRepository.findAllWithSubjectsAndMarks(); } @GetMapping("/students/{id}") public Student getStudentWithMarks(@PathVariable Integer id) { return studentRepository.findByIdWithSubjectsAndMarks(id); } }
内容的提问来源于stack exchange,提问作者DSM
相关产品推荐
相关产品推荐

