最近在做一个项目,关于考试系统的,后台用的SpringBoot框架,因为设计需求,需要在某个实体中使用一个联合主键,而同时这个实体由与其他两个实体有映射关系,于是开始了踩坑之旅。。。
先看一下实体,是一个考试的实体,联合主键是学生的id和试卷的id,同时一个学生可以有多场考试,一张试卷也可以用在多场考试中,所以还有两个一对多的关系映射。
//联合主键
@Embeddable
public class StudentPaperPk implements Serializable{
private int studentId;
private int paperId;
}
@Entity
public class Exam {
@EmbeddedId
private StudentPaperPk pk;
@Column(nullable = false,length = 255)
private String name;
@Column
private int timeSec;
@Column
private String result;
@Column
private Date date;
@ManyToOne(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinColumn(name = "student")
private Student student;
@ManyToOne(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinColumn(name = "paper")
private Paper paper;
}
@Entity
public class Student {
@Id
@GeneratedValue
private int id;
@Column(nullable = false,unique = true)
private String number;
@Column(nullable = false,length = 50)
private String name;
@Column(nullable = false,length = 50)
private String className;
@OneToMany(mappedBy = "student",fetch = FetchType.EAGER)
private Set<Exam> examSet;
}
@Entity
public class Paper {
@Id
@GeneratedValue
private int id;
@Column(nullable = false,length = 50)
private String paperName;
@Column
private String questions;
@ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinTable(name = "Paper_Question",
joinColumns = {@JoinColumn(name = "paperId",referencedColumnName = "id")},
inverseJoinColumns ={@JoinColumn(name = "questionId",referencedColumnName = "id")}
)
private Set<Question> questionSet;
@OneToMany(mappedBy = "paper",fetch = FetchType.EAGER)
private Set<Exam> examSet;
}
坑1:联合主键长度不超过1000byte
一开始使用的是学号和试卷号作为联合主键,而且这两者都是字符串,但是不管长度定多少,都会报长度超过1000byte的错误,所以我猜测是JPA联合主键不支持字符串。
坑2:注解位置
大家都清楚联合主键有三种注解方式,一开始我是将@EmbeddedId注解在pk的getter方法上,测试没问题,可以正常建表,但是当我加上一对多映射之后,会报下面这种错误:
org.hibernate.MappingException: Could not determine type for: com.model.Paper, at table: exam, for columns: [org.hibernate.mapping.Column(paper)]
原来注解位置要一致才能参照得到。
坑3:JPARepository的使用
大家都知道JPA提供了很方便的使用方式,只要定义一个接口继承JpaRepository,然后就可以用findByXXX来实现简单的查询了,对于联合主键的实体,有两个地方要注意:
1. JapRepository的实例化类型,第一个写实体类,第二个要写联合主键类,而不是Long。
2. find方法要写成findBy+实体类中的联合主键属性名+联合主键类中的属性名,如
public interface ExamRepository extends JpaRepository<Exam,StudentPaperPk> {
List<Exam> findByPkStudentId(int studentId);
}
在JPA中使用联合主键的上辈子都是折翼的天使!