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