Hibernate 多對(duì)多關(guān)聯(lián)映射
1. 前言
本節(jié)課,咱們一起繼續(xù)聊聊多對(duì)多關(guān)聯(lián)映射。通過(guò)本節(jié)課程,你將了解到:
- 多對(duì)多關(guān)聯(lián)映射的實(shí)現(xiàn);
- 雙向多對(duì)多關(guān)聯(lián)映射的實(shí)現(xiàn)。
2. 多對(duì)多關(guān)聯(lián)映射
首先了解表中的多對(duì)多關(guān)系,學(xué)生表中的數(shù)據(jù)和課程表中的數(shù)據(jù)就存在多對(duì)多關(guān)系。
一名學(xué)生可以選修多門(mén)課程,一門(mén)課程可以供多名學(xué)生選修。
數(shù)據(jù)庫(kù)通過(guò)主外鍵的機(jī)制描述表中的數(shù)據(jù)之間的關(guān)系。對(duì)于存在多對(duì)多關(guān)系的數(shù)據(jù)表,借助于中間表,分拆成兩個(gè)一對(duì)多(或者多對(duì)一)。
中間表的出現(xiàn),完美地表述了學(xué)生數(shù)據(jù)和課程數(shù)據(jù)之間的多對(duì)多關(guān)系。
數(shù)據(jù)庫(kù)的世界中有學(xué)生表、課程表,自然,Java 程序中就會(huì)有學(xué)生實(shí)體類(lèi)、課程實(shí)體類(lèi)。
不對(duì),好像遺漏了什么!
別忘了,表是有 3 張的(不是還有中間表嗎)。那么 Java 程序中的實(shí)體類(lèi)是不是應(yīng)該也要有 3 個(gè):
- 學(xué)生表對(duì)應(yīng)的實(shí)體類(lèi);
- 班級(jí)表對(duì)應(yīng)的實(shí)體類(lèi);
- 中間表對(duì)應(yīng)的實(shí)體類(lèi)。
至于中間表所對(duì)應(yīng)的實(shí)體類(lèi)是否應(yīng)該有:答案是可以有、也可以沒(méi)有。
如果中間表僅僅只是記載了學(xué)生和課程的關(guān)系,中間表的角色定位只是一個(gè)橋梁。這種情況下,Java 程序中可以不描述中間表結(jié)構(gòu)。
Java 程序中的實(shí)體類(lèi)不僅僅是用來(lái)模仿表結(jié)構(gòu),更多是看上了表中的數(shù)據(jù)。
如果中間表除了連接作用,還保存了程序中需要的數(shù)據(jù),則 Java 程序需要一個(gè)實(shí)體類(lèi)填充數(shù)據(jù)。如:
針對(duì)這 2 種情況,實(shí)體類(lèi)之間的映射關(guān)系會(huì)有微妙的變化。
3. 沒(méi)有中間表實(shí)體類(lèi)的映射
如果中間表僅僅只是充當(dāng)橋梁作用,沒(méi)有程序需要的實(shí)質(zhì)性數(shù)據(jù)時(shí),程序中可以沒(méi)有中間表對(duì)應(yīng)的實(shí)體類(lèi)。
學(xué)生和課程的關(guān)系,直接在學(xué)生類(lèi)和課程類(lèi)中體現(xiàn)彼此關(guān)系就可以:
新建課程實(shí)體類(lèi):
@Entity
public class Course {
private Integer courseId;
private String courseName;
private String courseDesc;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getCourseId() {
return courseId;
}
//省略其它代碼
}
因?yàn)橐幻麑W(xué)生對(duì)應(yīng)多門(mén)課程,在學(xué)生實(shí)體類(lèi)中添加集合屬性:
private Set<Course> courses;
完成程序級(jí)別上的關(guān)系描述后,還需告訴 Hibernate,實(shí)體類(lèi)中的集合屬性數(shù)據(jù)來(lái)自哪一張以及如何獲?。?/p>
為了把問(wèn)題簡(jiǎn)單化,在學(xué)生實(shí)體類(lèi)中只體現(xiàn)和課程的關(guān)系 ,前面映射內(nèi)容注釋或刪除。
學(xué)生實(shí)體類(lèi)的完整描述:
private Set<Course> courses;
@ManyToMany(targetEntity = Course.class)
@JoinTable(name = "score", joinColumns = @JoinColumn(name = "stuId", referencedColumnName = "stuId"),
inverseJoinColumns = @JoinColumn(name = "courseId", referencedColumnName = "courseId"))
public Set<Course> getCourses() {
return courses;
}
- @ManyToMany 告訴 Hibernate, course 集合中的數(shù)據(jù)來(lái)自課程表 ;
- @JoinTable 告訴 Hibernate 獲取課程表中數(shù)據(jù)時(shí)需要借助 score 中間表。分別描述中間表和學(xué)生表、課程表的連接字段。
在 Hibernate 主配置文件中修改或添加如下信息:
<property name="hbm2ddl.auto">create</property>
<mapping class="com.mk.po.Student" />
<mapping class="com.mk.po.Course" />
執(zhí)行下面的測(cè)試實(shí)例:
@Test
public void testGetStuAndCourse() {
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
}
查看 MySql 中的表:
切記把下面的信息修改回來(lái):
<property name="hbm2ddl.auto">update</property>
手工添加測(cè)試數(shù)據(jù):
好!通過(guò)測(cè)試實(shí)例見(jiàn)證 Hibernate 的神奇。
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
Student stu=(Student)session.get(Student.class, new Integer(1));
System.out.println("---------------------------");
System.out.println("學(xué)生姓名:"+stu.getStuName());
System.out.println("----------------------------");
System.out.println("學(xué)生選修課程數(shù):"+stu.getCourses().size());
return stu;
}
});
查看控制臺(tái)上面的輸出結(jié)果:
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=?
---------------------------
學(xué)生姓名:Hibernate
----------------------------
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=?
學(xué)生選修課程數(shù):2
Hibernate 構(gòu)建了兩條 SQL 語(yǔ)句,先是查詢到學(xué)生信息,需要課程信息時(shí),再通過(guò)中間表連接到課程表,查詢出課程相關(guān)信息。
可得出結(jié)論:默認(rèn)情況下,Hibernate 使用了延遲加載。如此做,Hibernate 是考慮了性能的。
4. 考慮中間表的映射
命名為 score 的中間表除了維系學(xué)生表和課程表的關(guān)系,還存儲(chǔ)有學(xué)生成績(jī)。如果程序中需要成績(jī)數(shù)據(jù),則需要?jiǎng)?chuàng)建一個(gè)成績(jī)實(shí)體類(lèi)。
現(xiàn)在就有了學(xué)生、課程、成績(jī) 3 個(gè) 實(shí)體類(lèi)。本質(zhì)是映射成兩個(gè)多對(duì)一(或一對(duì)多)的關(guān)系:
- 學(xué)生實(shí)體類(lèi)和成績(jī)實(shí)體類(lèi)一對(duì)多;
- 課程實(shí)體類(lèi)和成績(jī)實(shí)體類(lèi)的一對(duì)多:
具體的代碼就不再貼出,大家可參考一對(duì)多的課程內(nèi)容。
本節(jié)課不關(guān)心學(xué)生的課程成績(jī)是多少,只關(guān)心,如何通過(guò)學(xué)生找到課程,或通過(guò)課程找到學(xué)生。
有一個(gè)地方需要注意:
默認(rèn)情況下,中間表使用課程 ID 和學(xué)生 ID 聯(lián)合做主鍵,也可以提供一個(gè)自定義的主鍵。
5. 雙向多對(duì)多映射
前面實(shí)現(xiàn)了學(xué)生查詢到課程,如何在查詢課程時(shí),查詢到學(xué)生信息。很簡(jiǎn)單,在課程 PO 中,添加學(xué)生集合屬性:
// 學(xué)生信息
private Set<Student> students;
同樣使用 @ManyToMany 注解告訴 Hibernate 數(shù)據(jù)源頭及查詢方法:
private Set<Student> students;
@ManyToMany(targetEntity = Student.class, mappedBy = "courses")
public Set<Student> getStudents() {
return students;
}
執(zhí)行下面的測(cè)試實(shí)例:
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(1));
System.out.println("---------------------------");
System.out.println("課程名稱:"+course.getCourseName());
System.out.println("----------------------------");
System.out.println("選修此課程的學(xué)生數(shù):"+course.getStudents().size());
return course;
}
});
控制臺(tái)輸出結(jié)果:
Hibernate:
select
course0_.courseId as courseId1_0_0_,
course0_.courseDesc as courseDe2_0_0_,
course0_.courseName as courseNa3_0_0_
from
Course course0_
where
course0_.courseId=?
---------------------------
課程名稱:java
----------------------------
Hibernate:
select
students0_.courseId as courseId2_0_1_,
students0_.stuId as stuId1_2_1_,
student1_.stuId as stuId1_1_0_,
student1_.stuName as stuName2_1_0_,
student1_.stuPassword as stuPassw3_1_0_,
student1_.stuPic as stuPic4_1_0_,
student1_.stuSex as stuSex5_1_0_
from
score students0_
inner join
Student student1_
on students0_.stuId=student1_.stuId
where
students0_.courseId=?
選修此課程的學(xué)生數(shù):2
同樣,Hibernate 采用的是延遲加載模式。先查詢課程信息,當(dāng)開(kāi)發(fā)者需要學(xué)生信息時(shí),才構(gòu)建一條利用中間表進(jìn)入學(xué)生表的 SQL 查詢到學(xué)生信息。
可通過(guò)學(xué)生表查詢到課程表 ,也能從課程表查詢到學(xué)生表,這種多對(duì)多關(guān)聯(lián)映射稱為雙向映射關(guān)聯(lián)。
6. 小結(jié)
本節(jié)課和大家一起聊了聊多對(duì)多映射的實(shí)現(xiàn)。多對(duì)多是一種常見(jiàn)的關(guān)系。
多對(duì)多的映射實(shí)現(xiàn)可以有 2 種方案。使用中間表映射,或不使用中間表映射。
下一節(jié)課,繼續(xù)講解多對(duì)多映射中的添加、更新級(jí)聯(lián)操作,相信會(huì)給你帶來(lái)更多的驚喜。