You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在JPA中映射复合主键并将Student与Subject关联到StudentMarks实体

如何在JPA中映射复合主键并将Student与Subject关联到StudentMarks实体

看起来你已经找对了方向,但现有代码里有几个小问题需要调整,我来帮你梳理下正确的实现方式:

第一步:修正复合主键类StudentMarksId

你之前的主键类里有笔误,而且复合主键类必须正确实现equals()hashCode()方法(JPA需要用这个判断主键唯一性),同时要满足JPA对序列化和构造器的要求:

import jakarta.persistence.Column;
import java.io.Serializable;
import java.util.Objects;

@Embeddable
public class StudentMarksId implements Serializable {
    private static final long serialVersionUID = 1L;

    @Column(name = "student_id")
    private Long studentId;

    @Column(name = "subject_id")
    private Long subjectId;

    // JPA要求必须有无参构造器
    public StudentMarksId() {}

    // 带参构造器,方便创建实例
    public StudentMarksId(Long studentId, Long subjectId) {
        this.studentId = studentId;
        this.subjectId = subjectId;
    }

    // getter & setter
    public Long getStudentId() {
        return studentId;
    }

    public void setStudentId(Long studentId) {
        this.studentId = studentId;
    }

    public Long getSubjectId() {
        return subjectId;
    }

    public void setSubjectId(Long subjectId) {
        this.subjectId = subjectId;
    }

    // 重写equals和hashCode,保证主键唯一性判断正确
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        StudentMarksId that = (StudentMarksId) o;
        return Objects.equals(studentId, that.studentId) && Objects.equals(subjectId, that.subjectId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(studentId, subjectId);
    }
}

第二步:正确实现StudentMarks实体

你的实体类里存在几个误区:比如同时使用@EmbeddedId@Id(这是重复注解),还有用List关联StudentSubject(实际上每条成绩记录对应一个学生+一个科目)。下面是修正后的代码:

import jakarta.persistence.*;

@Entity
@Table(name = "student_marks")
public class StudentMarks {
    private static final long serialVersionUID = 4428298147790767663L;

    @EmbeddedId
    private StudentMarksId id;

    // 关联Student实体,@MapsId指定用主键类里的studentId字段映射到Student的id
    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("studentId")
    @JoinColumn(name = "student_id", insertable = false, updatable = false)
    private Student student;

    // 关联Subject实体,同理用主键类里的subjectId字段映射
    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("subjectId")
    @JoinColumn(name = "subject_id", insertable = false, updatable = false)
    private Subject subject;

    @Column(name = "marks", nullable = false)
    private int marks;

    @Column(name = "passing_status", nullable = false)
    private String passingStatus; // pass or fail

    // JPA要求的无参构造器
    public StudentMarks() {}

    // 带参构造器,方便快速创建成绩记录
    public StudentMarks(Student student, Subject subject, int marks, String passingStatus) {
        this.id = new StudentMarksId(student.getId(), subject.getId());
        this.student = student;
        this.subject = subject;
        this.marks = marks;
        this.passingStatus = passingStatus;
    }

    // getter & setter
    public StudentMarksId getId() {
        return id;
    }

    public void setId(StudentMarksId id) {
        this.id = id;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
        if (id == null) {
            id = new StudentMarksId();
        }
        id.setStudentId(student.getId());
    }

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
        if (id == null) {
            id = new StudentMarksId();
        }
        id.setSubjectId(subject.getId());
    }

    public int getMarks() {
        return marks;
    }

    public void setMarks(int marks) {
        this.marks = marks;
    }

    public String getPassingStatus() {
        return passingStatus;
    }

    public void setPassingStatus(String passingStatus) {
        this.passingStatus = passingStatus;
    }
}

关键注解说明

  • @MapsId("studentId"):告诉JPA,当前关联的Student实体的主键,要映射到复合主键类StudentMarksId里的studentId字段,这样设置student时,主键字段会自动同步
  • @JoinColumn里的insertable = false, updatable = false:因为主键字段已经由@EmbeddedId管理,这里避免关联关系重复更新数据库列,防止冲突
  • FetchType.LAZY:建议使用懒加载,避免不必要的关联实体查询,提升性能

可选:添加反向关联

如果你需要从StudentSubject端快速查询对应的成绩,可以在Student类里添加:

@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudentMarks> studentMarks = new ArrayList<>();

同理在Subject类里添加:

@OneToMany(mappedBy = "subject", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudentMarks> subjectMarks = new ArrayList<>();

备注:内容来源于stack exchange,提问作者xrcwrn

火山引擎 最新活动