《Hibernate学习笔记十二》学生、课程、分数关系的设计与实现
这个马士兵老师的Hibernate视频学习的一个题目,这里面要用到多对多、多对一的关联关系以及联合主键,因此觉得挺好的,自己写篇博文来记录下。
先考虑数据库表
1、学生表:为简单起见,只考虑了学生id和学生姓名,其中id为主键
2、课程表:为简单起见,只考虑了课程id和课程名称,其中id为主键
3、分数表
分数表有两种解决方案
3.1 第一种为:使用联合主键:student_id 和 course_id
3.2 第二种:不使用联合主键,而使用id作为主键
在这里,这篇博文所采取的方法是采用第二种方法:即不使用联合主键,使用id作为主键
在考虑实体类,以及它们的映射关系
一个学生可以选择多个课程,一个课程可以被多个学生选择,即学生和课程是一个多对多的关系。而一个学生可以有多个分数,因此,分数与学生是多对一的关系,同理,分数与课程也是多对一的关系。
下面开始设计
Student实体与Course类之间时多对多的关系,但是,一般情况下,我们需要通过学生来获取他的全部课程,因此,我们需要这样一个导向,有时,我们也需要通过课程,来提取选择这门课的学生,因此,我们建立双向关联。
Student类
@Entity
public class Student {
private int id;
private String name;
/*
* Student和Course是多对多的关系,
* 由于我们一般需要通过Student来获取Course,而不需要通过Course来获取Student,
* 因此我们建立一个单向导向
* */
private Set<Course> courses=new HashSet<Course>();
@ManyToMany(cascade=CascadeType.ALL)
//设置中间表,中间表必须的名称必须与Score一致
@JoinTable(name = "score",
joinColumns = @JoinColumn(name="student_id"),
inverseJoinColumns = @JoinColumn(name="course_id")
)
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Course类
@Entity
public class Course {
private int id;
private String name;
private Set<Student> students=new HashSet<Student>();
@ManyToMany(mappedBy="courses")
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Score类
Score与Student、Course都是多对一的关联关系,因此,我们需要建立ManyToOne的关联关系。
@Entity
@Table(name="score")
public class Score {
private int id;
/*
* Score与Student、Course都是多对一的关联关系,
* 且Student、Course是多对多的关联关系
* */
private Student student;
private Course course;
private int score;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@ManyToOne
@JoinColumn(name="student_id")
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@ManyToOne
@JoinColumn(name="course_id")
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
这样我们就完成了实体类的设计。
测试
通过如下的代码可以观察建表语句。
public void testSchema(){
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();
Metadata metadata = new MetadataSources(serviceRegistry).buildMetadata();
SchemaExport schemaExport = new SchemaExport();
schemaExport.create(EnumSet.of(TargetType.DATABASE), metadata);
}
建表语句如下:
从建表语句可以看出,生成的Score表是采用的是student_id和course_id的联合主键,而在Score实体类中,我们是采用的@Id来修饰的id,Hibernate并没有按照@id来进行生成表,而是根据Student类和Course实体类的中间表score来进行生成的。
由于生成的表与我们的目的我不符合,因此,我们可以选择手动建表,建表语句如下:
测试Save方法,即持久化对象
有一个学生,学了两门课,有两个成绩
@Test
public void testSave(){
/*
* 有一个学生,学了一门课,有一个成绩
* */
Student s1=new Student();
s1.setName("wu");
Course c1=new Course();
c1.setName("math");
Course c2=new Course();
c2.setName("english");
/*
* 困惑:加上如下的两条语句将会发出两条insert into score values(null,null);
* */
// s1.getCourses().add(c1);
// s1.getCourses().add(c2);
Score sc1=new Score();
sc1.setScore(98);
sc1.setStudent(s1);
sc1.setCourse(c1);
Score sc2=new Score();
sc2.setScore(77);
sc2.setStudent(s1);
sc2.setCourse(c2);
//开启事务进行持久化操作
Session s=sessionFactory.getCurrentSession();
s.beginTransaction();
s.save(s1);
s.save(c1);
s.save(c2);
s.save(sc1);
s.save(sc2);
s.getTransaction().commit();
}
正确的持久化的结果在数据库中结果如下:
在测试中,当我们在测试代码中,加上如下的两行代码(这两行代码在上面的代码块中已注释掉了),
s1.getCourses().add(c1);
s1.getCourses().add(c2);//这两行代码的意图就是将课程加入到学生中
就会得到如下的结果:即为我们自动生成了两行成绩为NULL的行数据。
遇到的问题:Field ‘id’ doesn’t have a default value
在完成这个测试的过程中,遇到了如下的问题:
原因是我们手动建表时,没有为主键id设置auto_increment.