常见的关联对应关系
- OneToMany
- ManyToOne
- OneToOne
- ManyToMany
关联关系是需要区分方向的,比如OneToMany,ManyToOne实际上是相等的。只是维护方不同而已
单向一对多关联
如,一个班级有多个学生。 这就是一种一对多的关系。如何实现呢?
在数据库中,可以通过添加主外键的关联,表现一对多的关系。
在java中,通过在一方持有多方的集合实现,即在“一”的一端中使用
代码实现
建立数据库和Java Bean
数据库的建立这里就省略了,可以根据Java Bean来建立。
接下来建立两个Java Bean文件
/**
* 一对多的映射 学生是单一一方
*/
public class Student implements Serializable {
private int sid;
private String sname;
private String sex;
private Grade grade;
// 省略setter/getter
}
/**
* 一对多映射 教室是多的一方
*/
public class Grade implements Serializable {
private int gid;
private String gname;
private String gdesc;
// 在一方定义一个多方集合
private Set students = new HashSet<>();
// 省略setter/getter
}
映射文件
首先需要在hibernate.cfg.xml文件中做相应的数据库配置以及文件映射,这里的代码就省略了。可以参考上一篇《Hibernate初探之单表映射》。
接下来重点讲一下两个Java Bean所对应的映射文件
CRUD
public void saveOne2Many() {
Grade grade = new Grade("Java大神班", "每个人都是厉害的角儿");
Student student = new Student("Jack", "男");
Student student2 = new Student("Rose", "女");
grade.getStudents().add(student);
grade.getStudents().add(student2);
session.save(grade);
session.save(student);
session.save(student2);
}
我们可以看到,通过将student设置进入grade的set集合中,从而就建立起了关联关系。那么sql语句是怎么样的呢?
-- 因为是id自增的,所以前两条语句先查询出grade,student的最大id值
Hibernate: select max(gid) from grade;
Hibernate: select max(SID) from student;
-- 插入grade, student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?);
Hibernate: insert into student (SNAME, sex, SID) values (?, ?, ?);
Hibernate: insert into student (SNAME, sex, SID) values (?, ?, ?);
-- 更新student的gid值
Hibernate: update student set gid=? where SID=?;
Hibernate: update student set gid=? where SID=?;
其实就是先分别插入grade和student,然后更新一下student的gid字段的值。
// 查找
@Test
public void findGradeAndStudent() {
Grade grade = (Grade) session.get(Grade.class, 1);
System.out.println(grade.getGname());
for(Student student : grade.getStudents()) {
System.out.println(student.getSname());
}
System.out.println("---Student---");
Student jack = (Student) session.get(Student.class, 1);
Student role = (Student) session.get(Student.class, 2);
System.out.println(jack.getSname() + "--" + jack.getGrade());
System.out.println(role.getSname() + "--" + role.getGrade());
}
此时通过查询grade是可以获取到grade中的student值的。但是查询Student是无法获取到Student中的grade值。
// 更新
@Test
public void updateStudent() {
Grade grade = (Grade) session.get(Grade.class, 1);
Student role = (Student) session.get(Student.class, 2);
role.setSname("肉丝");
grade.getStudents().add(role);
session.save(grade);
}
@Test
public void delStudent() {
Student jack = (Student) session.get(Student.class, 1);
session.delete(jack);
}
单向多对一
Students.java持久化类中添加private Grade grade;属性,并增加get/set方法。
Grade.hbm.xml文件中去除
标签。
Student.hbm.xml中添加
测试
/**
* 多对一
* 学生 -> 班级
*/
@Test
public void saveMany2One() {
Grade g = new Grade("德玛西亚", "Java软件开发一班");
Student stu1 = new Student("盖伦", "男");
Student stu2 = new Student("拉克丝","女");
//设置关联关系
stu1.setGrade(g);
stu2.setGrade(g);
session.save(g);
session.save(stu1);
session.save(stu2);
}
同样的,这里我们也观察一下保存数据时的sql语句
Hibernate: select max(gid) from grade
Hibernate: select max(SID) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
这里的大体流程和单向一对多的时候差不多,只是在新增student的时候就已经带上了gid的值
@Test
public void findStudent() {
Student student = (Student) session.get(Student.class, 1);
System.out.println(student.toString());
System.out.println("----");
Grade grade = (Grade) session.get(Grade.class, 1);
System.out.println(grade.getGname() + "--" + grade.getStudents());
}
查询语句在这里所查询到的Student是带有grade属性值的,而查询到的Grade则是没有students值的。
双向多对一/一对多
单向一对多,建立了班级到学生的关系
单向多对一,建立了学生到班级的关系
实际上,班级和学生的关系是相互的。
需要双方配置关联关系
在Grade.hbm.xml文件中,建立一对多关联关系:
在Student.hbm.xml中建立多对一关联关系
测试
@Test
public void find() {
Grade grade = (Grade) session.get(Grade.class, 1);
System.out.println(grade.getGname());
for(Student student : grade.getStudents()) {
System.out.println(student.getSname());
}
System.out.println("---Student---");
Student jack = (Student) session.get(Student.class, 1);
Student role = (Student) session.get(Student.class, 2);
System.out.println(jack.getSname() + "--" + jack.getGrade());
System.out.println(role.getSname() + "--" + role.getGrade());
}
此时查询出来的Student里面的grade是有值的了,查询的Grade里面的students也是有值的了。这就是双向的好处
@Test
public void save() {
Grade g = new Grade("德玛西亚", "Java软件开发一班");
Student stu1 = new Student("盖伦", "男");
Student stu2 = new Student("拉克丝","女");
// 设置班级->学生的一对多关系
g.getStudents().add(stu1);
g.getStudents().add(stu2);
// 设置学生->班级的多对一关系
stu1.setGrade(g);
stu2.setGrade(g);
session.save(g);
session.save(stu1);
session.save(stu2);
}
在这里可能会觉得有点奇怪,在单向关联的时候,只需要一方来设置关联就可以了,为什么双向关联的时候还要更复杂了呢。 其实这里是可以单独使用其中一种方式就可以的,不一定要同时设置关联关系。但是如果同时设置了关联关系会出现什么问题嘛? 我们可以来看一下sql语句
Hibernate: select max(gid) from grade
Hibernate: select max(sid) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (sname, sex, gid, sid) values (?, ?, ?, ?)
Hibernate: insert into student (sname, sex, gid, sid) values (?, ?, ?, ?)
Hibernate: update student set gid=? where sid=?
Hibernate: update student set gid=? where sid=?
由于同时设置了两种关联关系,所以sql语句上有点重复了。两条update语句对于班级对学生一对多关系时是多余的。虽然不会造成什么问题,但是对于性能上还是会有一定影响的。
为了解决掉上面的这个问题,我们可以使用inverse属性
inverse属性
inverse属性表示反转,是set节点的一个属性。
节点的inverse属性指定关系的控制方向,默认由one方来维护。
关联关系中,inverse=”false”,则为主动方,由主动方维护关联关系。
当前面的双向关联关系中,多方(学生方)自身会进行关联关系的维护,如果双方都来维护关联关系,性能上是有影响的(上面的例子多了两条update语句)。
可以将inverse属性设置为true,反转,由多方进行维护,这时,班级就不会进行关联关系的维护,这将有助于性能的改善。
Grade.hbm.xml
Hibernate: select max(gid) from grade
Hibernate: select max(SID) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
cascade属性
但是仔细观察上面的程序,在设置好关联关系后,班级中是有学生的信息的。班级是知道有哪些学生的。
那么,保存班级的时候,如果发现有学生在数据库中不存在,就应该自动把学生信息添加到数据库中,这个叫做级联操作。
当设置了cascade属性不为none时,Hibernate会自动持久化所关联的对象
属性值 | 含义和作用 |
---|---|
all | 对所有操作进行级联操作 |
save-update | 执行保存和更新操作时进行级联操作 |
delete | 执行删除时进行级联操作 |
none | 对所有操作不进行级联操作 |
cascade属性的设置会带来性能上的变动,需谨慎设置
属性值 | 含义和作用 |
---|---|
all | 对所有操作进行级联操作 |
save-update | 执行保存和更新操作时进行级联操作 |
delete | 执行删除时进行级联操作 |
none | 对所有操作不进行级联操作 |
/**
* 设置了cascade之后保存
*/
@Test
public void saveByCascade() {
Grade grade = new Grade("诺克萨斯", "哈哈哈哈嘿嘿嘿");
Student stu1 = new Student("德莱厄斯", "男");
Student stu2 = new Student("魔蛇之拥","女");
stu1.setGrade(grade);
stu2.setGrade(grade);
grade.getStudents().add(stu1);
grade.getStudents().add(stu2);
session.save(grade);
}
参考
Hibernate中的单向一对多关联
慕课网-Hibernate初探之一对多映射
相关代码