Hibernate 一對(duì)一關(guān)聯(lián)映射
1. 前言
本節(jié)課程和大家一起聊聊關(guān)聯(lián)映射。通過(guò)本節(jié)課程的學(xué)習(xí),你將了解到:
- 什么是關(guān)聯(lián)映射;
- 如何實(shí)現(xiàn)一對(duì)一關(guān)聯(lián)映射。
2. 關(guān)聯(lián)映射
關(guān)系數(shù)據(jù)庫(kù)中的數(shù)據(jù)以表為家,一張表一個(gè)家,一個(gè)家住一類數(shù)據(jù)。眾多表組成關(guān)系型社區(qū),關(guān)系型社區(qū)群體中的數(shù)據(jù)關(guān)系通過(guò)主外鍵方式描述。
表與表之間的數(shù)據(jù)根據(jù)彼此的關(guān)系可分為:
- 一對(duì)一關(guān)系: 如老公表和老婆表的關(guān)系;
- 一對(duì)多關(guān)系: 如用戶表和銀行賬號(hào)表關(guān)系;
- 多對(duì)一關(guān)系: 如銀行帳號(hào)表對(duì)用戶表關(guān)系;
- 多對(duì)多關(guān)系: 如學(xué)生表和課程表關(guān)系。
不管是哪種關(guān)系,都可以通過(guò)主外鍵方式聯(lián)系。
一對(duì)多、多對(duì)一本質(zhì)一樣,正如一塊硬幣的正反面,看待同一個(gè)事物的角度不同。
多對(duì)多通過(guò)中間表的方式拆分成兩個(gè)一對(duì)多(多對(duì)一)。
以上都是關(guān)系型數(shù)據(jù)庫(kù)中的基礎(chǔ)知識(shí),美好的回憶有助于鞏固。
開(kāi)發(fā)者使用 Hibernate 操作某一張表中的數(shù)據(jù)時(shí),有 2 件事情要做:
- 在程序中構(gòu)建一個(gè)與表結(jié)構(gòu)相似的類(這個(gè)類可稱為實(shí)體 [entity] 類);
- 使用注解或 XML 語(yǔ)法把類結(jié)構(gòu)和表結(jié)構(gòu)關(guān)聯(lián)映射起來(lái)(此時(shí)這個(gè)類可稱為 PO)。
有了 PO,Hibernate 就能在程序和數(shù)據(jù)庫(kù)之間進(jìn)行數(shù)據(jù)貿(mào)易往來(lái)。
2.1 新的需求總是接踵而至
如果程序要求 Hibernate 操作多張表中的數(shù)據(jù),并要求把獲取的數(shù)據(jù)封裝到對(duì)象中,又如何操作?
程序說(shuō):親愛(ài)的 Hibernate,幫我查詢一下學(xué)生信息和學(xué)生的家庭地址信息。
差點(diǎn)忘記告訴你:學(xué)生信息和學(xué)生的家庭地址信息分別存放在兩張表中,且一名學(xué)生只能有一個(gè)地址。
這是假設(shè),所以請(qǐng)不要糾結(jié)這種假設(shè)。
Hibernate 說(shuō): 可以。但有幾件事情必須按要求做好,否則干不了。
- 首先須確定數(shù)據(jù)庫(kù)中存在學(xué)生表和地址表,并確定兩者關(guān)系。使用地址編號(hào) addressId 字段作共同字段,addressId 在地址表中是主鍵,在學(xué)生表中是外鍵。
- 回到程序,構(gòu)建兩個(gè)類:Student 類、Address 類,添加注解描述。分別映射學(xué)生表、地址表。
Hibernate 說(shuō):還不夠。
數(shù)據(jù)庫(kù)中的表之間是有關(guān)系的,這種關(guān)系在 Student 類 和 Address 類 中也必須體現(xiàn)出來(lái)。
這也是 Hibernate 能查詢到多表中數(shù)據(jù)的核心要求。
2.2 PO 之間映射表之間的關(guān)系
從編碼層面上講,就是如何在 Student 類 與 Address 類 之間體現(xiàn)出數(shù)據(jù)庫(kù)表中數(shù)據(jù)之間的關(guān)系。
先從 Student 類 中開(kāi)始,在 Student 類 中添加一個(gè)屬性字段。
private Address address;
address 屬性是一個(gè) Address 類類型,數(shù)據(jù)庫(kù)不認(rèn)得這玩意兒,Hibernate 表示開(kāi)始要一個(gè)頭兩個(gè)大了,眩暈啦。
null 你先進(jìn)入學(xué)生表,然后根據(jù)學(xué)生表中的 addressId 進(jìn)入到地址表,找到對(duì)應(yīng)數(shù)據(jù)。
Hibernate 又沒(méi)有讀心術(shù),它如何知道你心里所想。
所以,需要通過(guò) XML 或注解語(yǔ)法把開(kāi)發(fā)者的想法告訴 Hibernate:
private Address address;
@OneToOne(targetEntity = Address.class)
@JoinColumn(name = "addressId")
public Address getAddress() {
return address;
}
-
@OneToOne 注解告訴 Hibernate :address 屬性的值要麻煩您先找到 學(xué)生表的 addressId,再辛苦去一下 地址表,把對(duì)應(yīng)的地址信息查詢出來(lái);
-
@JoinColumn:告訴 Hibernate 帶著 addressId 到地址表中查找。
主配置文件中添加如下信息:
<property name=*"hbm2ddl.auto"*>create</property>
<mapping class=*"com.mk.po.Student"* />
<mapping class=*"com.mk.po.Address"* />
在 Hibernate 創(chuàng)建完畢后,添加幾條測(cè)試數(shù)據(jù),操作完成后別忘記改回來(lái)。
此處操作自動(dòng)完成!相信聰明如你,一定沒(méi)問(wèn)題。
<property name="hbm2ddl.auto">update</property>
2.3 測(cè)試時(shí)間
查詢學(xué)生及學(xué)生的地址信息:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------學(xué)生信息---------------");
System.out.println("學(xué)生姓名:" + stu.getStuName());
System.out.println("-----------------地址信息-----------------");
System.out.println("學(xué)生家庭地址:"+stu.getAddress().getAddressName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
輸出結(jié)果:
Hibernate:
select
student0_.stuId as stuId1_1_1_,
student0_.addressId as addressI6_1_1_,
student0_.stuName as stuName2_1_1_,
student0_.stuPassword as stuPassw3_1_1_,
student0_.stuPic as stuPic4_1_1_,
student0_.stuSex as stuSex5_1_1_,
address1_.addressId as addressI1_0_0_,
address1_.addressName as addressN2_0_0_,
address1_.descript as descript3_0_0_
from
Student student0_
left outer join
Address address1_
on student0_.addressId=address1_.addressId
where
student0_.stuId=?
----------------學(xué)生信息---------------
學(xué)生姓名:Hibernate老大
-----------------地址信息-----------------
學(xué)生家庭地址:北京
Hibernate 使用 left outer join 構(gòu) 建一條 Sql 語(yǔ)句,一次性訪問(wèn) 2 張表,同時(shí)獲取了學(xué)生信息和地址信息;
請(qǐng)問(wèn) Hibernate,你能不能查詢地址信息時(shí),查詢出地址是哪個(gè)學(xué)生的。
可以,但是,需要在地址類中添加如下代碼:
private Student student;
@OneToOne(targetEntity=Student.class,mappedBy="address")
public Student getStudent() {
return student;
}
-
@OneToOne 注解的 targetEntity=Student.class 告訴 Hibernate,此屬性的值對(duì)應(yīng)的是學(xué)生表中的數(shù)據(jù);
-
在 Student 類已經(jīng)使用了 @OneToOne 映射。mappedBy=“address” 意思是說(shuō):Hibernate!student 類中已經(jīng)說(shuō)明的夠清楚了吧,這里就不要我再啰嗦了。
測(cè)試實(shí)例:
try {
transaction = session.beginTransaction();
Address address = (Address) session.get(Address.class, new Integer(1));
System.out.println("----------------地址信息---------------");
System.out.println("地址信息:" + address.getAddressName());
System.out.println("-----------------學(xué)生信息-----------------");
System.out.println("學(xué)生姓名:" + address.getStudent().getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
輸出結(jié)果:Hibernate 自動(dòng)構(gòu)建了多表查詢語(yǔ)句,一次性從數(shù)據(jù)庫(kù)獲取所有數(shù)據(jù)。
Hibernate:
select
address0_.addressId as addressI1_0_1_,
address0_.addressName as addressN2_0_1_,
address0_.descript as descript3_0_1_,
student1_.stuId as stuId1_1_0_,
student1_.addressId as addressI6_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
Address address0_
left outer join
Student student1_
on address0_.addressId=student1_.addressId
where
address0_.addressId=?
----------------地址信息---------------
地址信息:北京
-----------------學(xué)生信息-----------------
學(xué)生姓名:Hibernate老大
無(wú)論是從學(xué)生表查詢到地址表,還是從地址表查詢到學(xué)生表。只要有足夠的信息告訴 Hibernate 如何關(guān)聯(lián)到數(shù)據(jù)庫(kù)中對(duì)應(yīng)的表,Hibernate 都會(huì)如你所愿。
3. 關(guān)聯(lián)映射中的延遲加載
關(guān)聯(lián)多表查詢時(shí)可選擇是否啟用延遲加載。
PO 之間的映射,意味著 Hibernate 不僅能查詢到指定表中數(shù)據(jù),還能查詢相關(guān)聯(lián)表中的數(shù)據(jù)。
但,有時(shí)只需要查詢學(xué)生基本信息,并不需要地址信息,或者地址信息并不需要馬上查詢出來(lái),能不能告訴 Hibernate,只查詢學(xué)生信息,暫且別查詢地址信息。
同樣,有時(shí)只需要查詢所在地址,并不關(guān)心地址對(duì)應(yīng)學(xué)生信息。
可以啟動(dòng)關(guān)聯(lián)映射中的延遲加載實(shí)現(xiàn)上面的需求。
學(xué)生類中修改代碼如下:
@OneToOne(targetEntity = Address.class,fetch=FetchType.LAZY)
@JoinColumn(name = "addressId")
public Address getAddress() {
return address;
}
@OneToOne 注解有 fetch 屬性,為枚舉類型,其值可選擇:
- FetchType.LAZY;
- FetchType.EAGER。
其作用便是告訴 Hibernate,是否延后或立即查詢相關(guān)聯(lián)表中的數(shù)據(jù)。
執(zhí)行下面測(cè)試代碼:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------學(xué)生信息---------------");
System.out.println("學(xué)生姓名:" + stu.getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看結(jié)果:
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.addressId as addressI6_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é)生信息---------------
學(xué)生姓名:Hibernate老大
Hibernate 只構(gòu)建了一條簡(jiǎn)單的 Sql 語(yǔ)句, 用于查詢學(xué)生信息。
繼續(xù)執(zhí)行下面測(cè)試實(shí)例:
try {
transaction = session.beginTransaction();
Student stu = (Student) session.get(Student.class, new Integer(1));
System.out.println("----------------學(xué)生信息---------------");
System.out.println("學(xué)生姓名:" + stu.getStuName());
System.out.println("-----------------地址信息-----------------");
System.out.println("學(xué)生家庭地址:" + stu.getAddress().getAddressName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
輸出結(jié)果:
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.addressId as addressI6_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é)生信息---------------
學(xué)生姓名:Hibernate老大
-----------------地址信息-----------------
Hibernate:
select
address0_.addressId as addressI1_0_0_,
address0_.addressName as addressN2_0_0_,
address0_.descript as descript3_0_0_
from
Address address0_
where
address0_.addressId=?
學(xué)生家庭地址:北京
Hibernate 分別構(gòu)建了 2 條簡(jiǎn)單的查詢 Sql 語(yǔ)句,可得出結(jié)論:
- 只有當(dāng)需要獲取地址信息時(shí),才會(huì)構(gòu)建 Sql 語(yǔ)句查詢地址表;
這就是關(guān)聯(lián)映射中的延遲加載。
- @OneToOne 默認(rèn)情況下是采用立即策略,通過(guò)構(gòu)建多表查詢語(yǔ)句一次性全部查詢。
4. 小結(jié)
本節(jié)課講解了如何通過(guò)實(shí)現(xiàn)實(shí)體類之間的映射,以保證 Hibernate 能正常訪問(wèn)開(kāi)發(fā)者所需要的關(guān)聯(lián)表中的數(shù)據(jù)。其中有些細(xì)節(jié)暫未深究。
當(dāng)介紹一對(duì)多、多對(duì)多映射關(guān)系時(shí),再抽絲剝繭般展開(kāi)。
OK!意猶未盡之處,下一節(jié)課程再暢聊。