如何在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关联Student和Subject(实际上每条成绩记录对应一个学生+一个科目)。下面是修正后的代码:
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:建议使用懒加载,避免不必要的关联实体查询,提升性能
可选:添加反向关联
如果你需要从Student或Subject端快速查询对应的成绩,可以在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




