Hibernate 双向多对多关联映射
1. 前言
通过本节课程的学习,你将发现关联对象之间的微妙关系。相信这种关系对你更深入地认识 HIbernate 有很大的帮助。
通过本节课程,你将了解到:
- 多对多双向关联映射中哪一方是关系维系者;
- 级联操作与关系维系者。
2. 关系维系者
新学期开始了,同学们选择了各自喜欢的课程,现在为学生添加选修课程的任务就要落在 Hibernate 的身上。一起来看看 Hibernate 是如何完成这个任务。
行动之前,先假设一个需求:
“Hibernate” 同学觉得自己选修的课程太难了,现在想重新选择。
重新选修之前,先删除原来选修的内容:
执行流程分析:
- 进入学生表,查询到 “Hibernate” 同学的信息;
- 删除 “Hibernate” 同学的所有选修课程。
2.1 解除级联对象之间的关系
删除方式有两种:
第一种解除方案
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// 查询学生
Student student =(Student)session.get(Student.class, new Integer(1));
// 查询Java课程
Course javaCourse = (Course) session.get(Course.class, new Integer(1));
// 查询C课程
Course ccourse = (Course) session.get(Course.class, new Integer(2));
// 解除关系
student.getCourses().remove(javaCourse);
student.getCourses().remove(ccourse);
return null;
}
});
查询到 ”Hibernate“ 同学的信息,注意,此时 student 对象处于持久化状态,意味着 student 对象在程序世界的行为可以同步到数据库中。
查询 ”Hibernate“ 同学选修的 Java 和 C 两门课程,此时保存这两个课程信息的对象也处于持久化状态。
使用如下代码解除学生和课程之间的关系:
student.getCourses().remove(javaCourse);
student.getCourses().remove(ccourse);
因为学生对象、课程对象都处于持久化状态。它们在程序世界中的一言一行都会同步到数据库中。
既然在程序世界解除了彼此之间的关系,在数据库中,中间表中的关系描述数据也会自动删除。
从控制台上所显示出来的 SQL 语句其实也知道删除已经成功,这个就不贴出来了。
进入 MySql 验证一下:
中间表中已经不存在和 ”Hibernate“ 同学相关的课程信息。
但是,此时你可能会有一个想法,刚刚是以学生对象为主动方,向课程对象提出了分手,那么,能不能以课程方为主动方提出分手呢?
试一下便知,测试之前,先恢复原来的内容:
执行下面的实例代码:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// Hiberante学生
Student student =(Student)session.get(Student.class, new Integer(1));
// 查询Java
Course javaCourse = (Course) session.get(Course.class, new Integer(1));
// 查询C
Course ccourse = (Course) session.get(Course.class, new Integer(2));
// 解除关系,以课程对象为主动方
javaCourse.getStudents().remove(student);
ccourse.getStudents().remove(student);
return null;
}
});
可能会让你失望,这次操作对数据库没有任何影响。
可见,分手只能是由学生对象提出来。
虽然在前面课程中,咱们配置了学生类和课程类的双向多对多关联映射,但是,两者之间只能有一个主动方,这里要了解,所谓主动方,就是关系的维系者。
关系的建立和解除只能由主动方提供。是不是有点像霸道总裁的狗血故事。
第二种解除方案
Ok!一起继续了解第 2 种方案。
不管是哪种方案,切记,只能是由主动方提出分手。
行事之前,一定要先检查数据是否存在。
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// Hibernate学生
Student student =(Student)session.get(Student.class, new Integer(1));
//解除关系
student.getCourses().removeAll(student.getCourses());
return null;
}
});
和第一种方案相比较,不再查询课程信息,由学生对象单方面一次性解除关系。
student.getCourses().removeAll(student.getCourses());
return null;
执行结果没有什么意外,程序世界中关系的解除操作同步到了数据库中。
一起看看控制台上输出的信息:
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.stuName as stuName2_1_0_,
student0_.stuPassword as stuPassw3_1_0_,
student0_.stuPic as stuPic4_1_0_,
student0_.stuSex as stuSex5_1_0_
from
Student student0_
where
student0_.stuId=?
Hibernate:
select
courses0_.stuId as stuId1_1_1_,
courses0_.courseId as courseId2_2_1_,
course1_.courseId as courseId1_0_0_,
course1_.courseDesc as courseDe2_0_0_,
course1_.courseName as courseNa3_0_0_
from
score courses0_
inner join
Course course1_
on courses0_.courseId=course1_.courseId
where
courses0_.stuId=?
Hibernate:
delete
from
score
where
stuId=?
大家可以看到,Hibernate 接收到解除操作后,立即由中间表连接到课程表,把相关课程信息从中间表中抺出。
一切进行得简单而有序:
- 记住,持久化对象的行为可以同步到数据库中去;
- 多对多双向关联映射中,有主动方和被动方一说。
2.2 建立级联对象之间的关系
好!现在为 ”Hibernate“ 同学选修新的课程。比如说,想选择 DB 课程和 JAVA 课程。
天呀,刚刚不是才解除了 JAVA 课程吗。你管得着吗,咱们就是这么任性,想解除就解除,想建立就建立 ,谁叫 Hibernate 这么方便呢。
有了前面的知识,应该难不倒我们了。
现在来看看 Hibernate 实例:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// 添加新学生
Student student =(Student)session.get(Student.class, new Integer(1));
// 查询Java
Course javaCourse = (Course) session.get(Course.class, new Integer(1));
// 查询C
Course dbCourse = (Course) session.get(Course.class, new Integer(3));
// 确定关系
student.getCourses().add(javaCourse);
student.getCourses().add(dbCourse);
return null;
}
});
关系还是在程序中直接体现,并且是由学生对象维护。
可以进入数据库,查看一下:
还是再叮嘱一下,关系只能由学生方维护的。
此处,应该有疑问,为什么只能是学生方,而不能是课程方,难道在 Java 的世界里也有命运一说。
这个命运的安排是由开发者来决定的,在进行关联注解时,那一方使用了 mappedBy 属性,则这一方就是被动方,很好理解,这个属性本身的含义就是说,听对方的。
所以说,谁是霸道总裁,看开发者的心情。理论上讲,多对多中,两者应该是平等关系。
刚刚的解除和重新建立都是对已经存在的学生进行的。
如果班里转来了一名叫 ”HibernateTemplate“ 的新学生,他想选修 JAVA 和 DB,则应该怎么操作呢?
很简单啊!
- 学生没有,添加就是;
- 课程信息有,从表中查询就是;
- 关系不存在,建立就是。
下面便是如你所想的实例代码:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// 添加新学生
Student student = new Student("HibernateTemplate", "男");
// 查询Java课程
Course javaCourse = (Course) session.get(Course.class, new Integer(1));
System.out.println(javaCourse.getCourseName());
// 查询DB课程
Course dbCourse = (Course) session.get(Course.class, new Integer(3));
System.out.println(dbCourse.getCourseName());
// 确定关系
student.getCourses().add(javaCourse);
student.getCourses().add(dbCourse);
return null;
}
});
是的,这个代码本身没有问题。
但是,这里会有一个小坑,这个坑没有填的话,你可能会测试不成功。
在学生类中,一定要记住对下面的属性进行实例化:
private Set<Course> courses=new HashSet<Course>();
否则就会有空指针异常抛出来。前面没有,是因为数据库中有数据,Hibernate 帮咱们自动实例化了。
但现在是一个新学生,Hiberante 可不会帮你实例化他的课程集合属性。
也就是不能任何时候都依靠 Hibernate,它也有顾全不到的地方。为了让你宽心,还是看一下数据库中数据吧:
3. 级联删除
前面讲解双向一对多的时候,也提到了级联删除。最大的印象就是,如果双方都打开了级联删除,删除时就如同推倒了多米诺骨牌的第一张牌,整个数据链都会删除。
多对多关联比一对多关联多了一张中间表,在进行级联删除的时候,到底会发生什么事情?
在此也有必要拿出来说一说。
为了不让事情的发展如山崩一样不可控制,先打开学生类的级联操作功能:
private Set<Course> courses=new HashSet<Course>();
@ManyToMany(targetEntity = Course.class,cascade=CascadeType.ALL)
@JoinTable(name = "score", joinColumns = @JoinColumn(name = "stuId", referencedColumnName = "stuId"),
inverseJoinColumns = @JoinColumn(name = "courseId", referencedColumnName = "courseId"))
public Set<Course> getCourses() {
return courses;
}
这里使用 CascadeType.ALL。
来一段测试实例,删除刚才添加的 HibernateTemplate 同学。
他会说我好悲惨,才进来没有多久。
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
// 查询学生
Student student =(Student)session.get(Student.class, new Integer(23));
session.delete(student);
return null;
}
});
无惊无喜,一切按照预先的设想进行。
删除学生时,中间表中与此学生有关联的信息,也就是说此学生选修的课程信息也自动被删除了。
但是,会有一个想法,如果删除课程,则中间表中记录的与此课程有关的信息是否会自动删除呢?
OK!开始行动之前,可别忘记在课程类中打开级联操作选项:
嘿嘿!现在两边的级联操作功能都已经打开。
private Set<Student> students=new HashSet<Student>();
@ManyToMany(targetEntity = Student.class, mappedBy = "courses",cascade=CascadeType.ALL)
public Set<Student> getStudents() {
return students;
}
打开后,执行删除 C 课程的实例,谁让 C 不好学了。
HibernateTemplate<Course> hibernateTemplate = new HibernateTemplate<Course>();
hibernateTemplate.template(new Notify<Course>() {
@Override
public Course action(Session session) {
// 查询学生
Course course =(Course)session.get(Course.class, new Integer(2));
session.delete(course);
return null;
}
});
这只是一个很简单的代码,但是却发生如雪崩一样的事件。
到底发生了什么事情?
大家进入 MySql 看看就知道了。
3张表中空空如也,所有数据都没有了。
就如同前面讲解一对多的级联删除一样。同样适用于多对多关联映射之中。
因两边都已经打开了级联,删除操作如同无法控制的坏情绪,删除课程时,以中间表为连接,反复来往于三张表,把相关信息全部删除。
所以,使用级联时一定要小心,否则,小心脏真的有点受不了。
4. 小结
好了,本节课可以结束了,通过本节课,大家了知道无论是双向一对多,还是双向多对多,都会有一个主动方。
在双向多对关联中,主动方有解除关系能力。
级联操作功能用得好,能一劳永逸。但是,如果没有用好,则会如同打开了潘多拉魔盒,发生雪崩一样的灾难。
- 还没有人评论,欢迎说说您的想法!