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,則應該怎么操作呢?
很簡單?。?/p>
- 學生沒有,添加就是;
- 課程信息有,從表中查詢就是;
- 關系不存在,建立就是。
下面便是如你所想的實例代碼:
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. 小結
好了,本節課可以結束了,通過本節課,大家了知道無論是雙向一對多,還是雙向多對多,都會有一個主動方。
在雙向多對關聯中,主動方有解除關系能力。
級聯操作功能用得好,能一勞永逸。但是,如果沒有用好,則會如同打開了潘多拉魔盒,發生雪崩一樣的災難。