生活中的对象实体之间往往存在关系,有一对一、一对多、多对多的关系,比如一个课程班级里有多个学生就是一对多的关系。在数据库中可以通过给学生表添加外键指向班级id的方式来表示学生包含多个学生的关系,对应的在Java中,可以创建Course、Student两个类来表示班级和学生,那么如何建立二者之间的关系呢?
如下所示为数据库中的courses表和students表
一对多映射
第一种方式是一对多的映射,例如建立从班级(“一”)-->学生(“多”)的映射。通过可以在Course类添加一个Set集合用于保存多个Student对象,并且在hibernate中设置Course指向Student。
在IDEA中分别为两个类添加Hibernate数据库映射,会自动生成CourseEntity类与StudentEntity类文件和对应的hbm.xml映射文件,并且在hibernate.cfg.xml文件中完成注册。之后在CourseEntity中手动添加Set属性students和其get/set方法
public class CoursesEntity {
private int id;
private String name;
private Integer hours;
private Set<StudentEntity> students=new HashSet<StudentEntity>();
......
public Set<StudentEntity> getStudents() {
return students;
}
public void setStudents(Set<StudentEntity> students) {
this.students = students;
}
接着需要修改CourseEntity.hbm.xml文件,在其中添加set属性students,指定其对应的数据表和与本数据表courses相关联的外键,还有该属性所对应的Java实体类
<class name="entity.CoursesEntity" table="courses" schema="test">
<id name="id" column="id"/>
<property name="name" column="name"/>
<property name="hours" column="hours"/>
<set name="students" table="students"> <!--配置students集合对应的表-->
<key column="course_id"></key> <!--表中对应的外键-->
<one-to-many class="entity.StudentEntity"/> <!--对应的Java类-->
</set>
</class>
之后在测试方法中创建一个course课程对象,和两个学生对象s1、s2,并在课程的students集合中调用Set的add()添加两个学生对象,最后将这些对象都保存在数据库中。注意这里依然使用的是Junit测试单元,所以关于session和factory等的创建和关闭都在之前的setup和tearDown中完成了。
@Test
void testCourse(){
//创建课程和学生对象
CoursesEntity course=new CoursesEntity(2,"Data Structure",72);
StudentEntity s1=new StudentEntity(1003,"小明",15);
StudentEntity s2=new StudentEntity(1004,"小花",14);
//在课程的students的Set集合中添加两个学生对象
course.getStudents().add(s1);
course.getStudents().add(s2);
//保存课程和学生对象到数据库
session.save(course);
session.save(s1);
session.save(s2);
}
查看数据库可见course表中已经添加了对应的课程,而且students表中不仅添加了两个学生,而且其外键course_id都指向了对应的课程id
查看数据也很方便,可以通过course返回的Set集合遍历其中的选课学生,而hibernate会自动去students表中取出对应的学生信息
@Test
void findCourse() {
CoursesEntity course = session.load(CoursesEntity.class, 2);
System.out.println("课程名称:"+course.getName());
//通过course返回学生Set集合遍历学生信息
Set<StudentEntity> students = course.getStudents();
System.out.println("课程学生:");
for (StudentEntity student : students)
System.out.print(student.getId() + ':' + student.getName());
}
从课程中删除一个学生通过调用Set的remove()方法,数据库会自动修改学生course_id的外键为null
CoursesEntity course = session.load(CoursesEntity.class, 2);
Set<StudentEntity> students = course.getStudents(); //获取课程学生的Set集合
StudentEntity s=session.load(StudentEntity.class,1003); //获取指定学生对象
//从选课列表删除指定学生
students.remove(s);
session.save(s); //保存操作
双向映射
除了配置Course到Student的映射,反过来也可以配置Student到Course的映射。这样就实现了两个对象类型之间的双向映射。如下进行Student --> Course的映射:
首先在StudentEntity类中添加表示课程的属性的变量course和get/set方法
public class StudentEntity {
private int id;
private String name;
private Integer age;
private Address address;
private CoursesEntity course; //添加表示所在课程的属性
......
public CoursesEntity getCourse() {
return course;
}
public void setCourse(CoursesEntity course) {
this.course = course;
}
接着配置”多“的hbm.xml文件,name为变量名,class为指向的类,column为数据表中的外键
<class name="entity.StudentEntity" table="students" schema="test">
<id name="id" column="id"/>
<property name="name" column="Name"/>
<property name="age" column="Age"/>
<component name="address" class="entity.Address">
<property name="city" column="city"/>
<property name="street" column="street"/>
</component>
<!--配置Student指向Course的映射-->
<many-to-one name="course" class="entity.CoursesEntity" column="course_id"/>
</class>
接下来在代码中创建Course与Student对象并进行双向映射
@Test
void testCourse() {
//创建课程和学生对象
CoursesEntity course = new CoursesEntity(2, "Data Structure", 72);
StudentEntity s1 = new StudentEntity(1003, "小明", 15);
StudentEntity s2 = new StudentEntity(1004, "小花", 14);
//添加Course --> Student映射
course.getStudents().add(s1);
course.getStudents().add(s2);
//添加Student --> Course映射
s1.setCourse(course);
s2.setCourse(course);
//保存课程和学生对象到数据库
session.save(course);
session.save(s1);
session.save(s2);
}
维护双向映射:通过以上操作就实现了数据表的双向映射,但是查看HIbernate执行语句如下所示,在进行了students表的两次insert操作之后又进行了两次update操作。这是由于在Course在维护一对多关系时执行力insert操作,之后student在维护多对一关系时又进行了update操作,而这样的操作是没有必要且影响执行效率。很明显这是由于双方都在维护一对多关系而造成的,因此我们希望仅由一方维护一对多关系,在Course的配置文件中设置set的inverse属性为false,代表关系由“多”的那一方来维护。
Hibernate: insert into courses (name, hours, id) values (?, ?, ?)
Hibernate: insert into students (Name, Age, city, street, course_id, id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into students (Name, Age, city, street, course_id, id) values (?, ?, ?, ?, ?, ?)
Hibernate: update students set course_id=? where id=?
Hibernate: update students set course_id=? where id=?
级联操作:在通过session保存对象时,我们不仅执行了session.save(course),还又执行了session.save(s1)来保存学生,Course对象里已经包含了Student,应该自动一起进行保存,而不是我们手动再保存,这就需要级联操作来设置。可以对Course配置文件中的set进行配置,设置cascade属性如下
如下为设置inverse和cascade:
<class name="entity.CoursesEntity" table="courses" schema="test">
<id name="id" column="id"/>
<property name="name" column="name"/>
<property name="hours" column="hours"/>
<!--对inverse、cascade属性进行设置-->
<set name="students" table="students" inverse="false" cascade="save-update">
<key column="course_id"></key>
<one-to-many class="entity.StudentEntity"/>
</set>
</class>