You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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_id
  • Subject:存储科目信息,主键为id
  • StudentMark:存储学生科目成绩,包含student_id(关联Student.user_id)、subject_id(关联Subject.id)、mark、description字段,主键为grade_id
  • student_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 00:55:39