Hibernate3 第二天
第一天回顧:
今天內容安排:
學習目標:
官方描述:
Hibernate 將操作的PO對象分為三種狀態:
搭建測試環境:創建項目Hibernate3_day02
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- 配置java類與數據表的對應關系
name:java類名
table:表名
-->
<class name="cn.itcast.a_postate.Customer" table="t_customer">
<!-- 配置主鍵 -->
<id name="id">
<generator class="native"></generator>
</id>
<!-- 配置其他屬性 -->
<property name="name"></property>
<property name="age"></property>
</class>
</hibernate-mapping>
5 創建TestPOState類,描述對象的三狀態。
在類中編寫測試testSave方法:代碼如下:
@Test
public void testSave(){
//瞬時態:
//特點:沒有OID,new出來的一個對象,不與session關聯,不受session管理,數據庫中沒有對應的記錄
Customer customer = new Customer();
customer.setName("rose");
customer.setAge(18);
System.out.println(customer);
Session session = HibernateUtils.openSession();
session.beginTransaction();
//在save執行之前,customer都只是瞬時態
//當save執行之後,customer就處於持久態
session.save(customer);
//持久態
// /特點:有OID,數據庫存在對應的記錄,與session關聯,受session管理
System.out.println(customer);
session.getTransaction().commit();
session.close();
//只要session已關閉,那麼久處於脫管態
//有OID,數據庫中存在對應的記錄,但是不與session關聯,不受session管理
System.out.println(customer);
}
三種狀態的區別分析:
瞬時態和脫管態:相同點是都和Session沒關聯,不同點是瞬時態沒有OID,而脫管態有OID。
【思考】
通過上述的分析,發現,瞬時態和脫管態對象就差一個OID,那麼瞬時態的對象中給主鍵ID屬性賦值後就是脫管態了麼?
未必!
首先,需要區分持久化標識OID和對象中主鍵ID屬性的關系:
因此:OID和PO對象中主鍵ID屬性的區別就是:數據庫存在不存在,如果存在就是OID,如果不存在,那就是個ID屬性而已。
瞬時態和脫管態的區別總結:
例如:
Customer對象具有Id屬性值,如果數據庫中不存在,則該對象還是瞬時態對象,如果數據庫中存在,則認為是脫管態的。
【三者的區別最終總結】:
對於三者:在session中存在的,就是持久化對象,不存在的就是瞬時或脫管對象。
對於瞬時和脫管對象:有oid(持久化標識)的就脫管對象,沒有的就是瞬時對象。
OID一定是與數據庫主鍵一一對應的
是否有持久化標識OID
session是否存在
數據庫中是否有
瞬時態對象-臨時狀態
n
n
n
持久態對象
y
y
y/(n:沒有提交)
脫管態對象-游離
y
n
y
持久化對象狀態轉換圖(官方規范):
【分析】:(各狀態對象的獲取方式以及不同狀態之間轉換的方法介紹):
如何直接獲得 --- new 出來
轉換到持久態 ---- save、saveOrUpdate 保存操作
轉換到脫管態 ---- setId 設置OID持久化標識(這個id是數據庫中存在的)
如何直接獲得 ---- 通過session查詢方法獲得 get、load、createQuery、createSQLQuery、createCriteria
轉換到瞬時態 ---- delete 刪除操作 (數據表不存在對應記錄 )(其實還有id,只是不叫OID)
轉換到脫管態 ---- close 關閉Session, evict、clear 從Session清除對象
如何直接獲得 ----- 無法直接獲得 ,必須通過瞬時對象、持久對象轉換獲得
轉換到瞬時態 ---- 將id設置為 null,或者手動將數據庫的對應的數據刪掉或者將id修改成數據庫中不存在的
轉換到持久態 ---- update、saveOrUpdate、lock (對象重新放入Session ,重新與session關聯)
在Hibernate所有的操作只認OID,如果兩個對象的OID一致,它就直接認為是同一個對象。
又稱為:hibernate一級緩存、session緩存、session一級緩存
緩存的介質是一般是內存(硬盤),用來存放數據,當第一次查詢數據庫的時候,將數據放入緩存,當第二次繼續使用這些數據的時候,就不需要要查詢數據庫了,直接從緩存獲取,
在Session接口中實現了一系列的java集合,這些java集合構成了Session的緩存,只要Session的生命周期沒有結束,session中的數據也就不會被清空。
將數據緩存到內存或者硬盤上,訪問這些數據,直接從內存或硬盤加載數據,無需到數據庫查詢。
好處: 快! 降低數據庫壓力。
Session中對象集合(map),在Session創建時,對象集合(map)也就創建,緩存保存了Session對象數據,當Session銷毀後,集合(map)銷毀, 一級緩存釋放 !
只要是持久態對象,都會保存在一級緩存 (與session關聯的本質,就是將對象放入了一級緩存)
一級緩存的作用:第一次get/load的時候,肯定會發出sql語句,查詢數據庫,(此時會將數據放入一級緩存),只要session不關閉,
第二次get/load的時候,直接從緩存中讀取數據,不會發出sql語句,查詢數據庫(這裡的數據指的是同一條記錄:OID相等)
【示例】證明一級緩存的存在性!
通過多次查詢同一個po對象數據,得到同一個對象,且第二次不再從數據庫查詢,證明一級緩存存在。
@Test
public void testFirstCacheExist(){
/**
* 證明一級緩存的存在性
* 證明思路:
* 緩存的作用就是用來少查數據庫的,提高訪問速度
* 第一步,通過get/load查詢數據,由於是第一次查詢,所以必然發出sql語句,查詢數據庫
* 第二步,不關閉session,繼續使用當前的session去get/load數據,觀察是否發出sql語句
* 如果不發出,表明是從一級緩存取出的數據
*/
Session session = HibernateUtils.openSession();
session.beginTransaction();
//第一步:此時必然發出sql語句,然後自動將數據放入一級緩存
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer);
//第二步:此時不會發出sql語句,直接從一級緩存獲取數據
Customer customer2 = (Customer) session.get(Customer.class, 1);
System.out.println(customer2);
session.getTransaction().commit();
session.close();
}
測試(同一個對象):
一級緩存的生命周期就是session的生命周期,不能跨Session,可以說,一級緩存和session共存亡!
【示例】
使用兩個不同Session來測試生命周期。(一級緩存和session共存亡)
@Test
public void testFirstCachelifecycle(){
/**
* 一級緩存的聲明周期:與session同生命共存亡
* 如何證明一級緩存的生命周期?
* 只要證明數據不能跨session
* 1 獲取session1,通過session1拿到customer對象,此時必然發出sql語句,關閉session1
* 2 獲取session2,通過session2繼續抓取customer對象,觀察第二次是否發出sql語句
* 如果發出,,表名session1銷毀的時候,把數據也銷毀了
*/
Session session1 = HibernateUtils.openSession();
session1.beginTransaction();
//此時必然發出sql語句,因為是第一次查詢
Customer customer = (Customer)session1.get(Customer.class, 1);
System.out.println(customer);
// 此處如果需要查詢Customer,會發sql語句嗎?答:不會,直接走一級緩存
//也能證明數據成功存入了一級緩存
Customer customer2 = (Customer)session1.get(Customer.class, 1);
System.out.println(customer2);
session1.getTransaction().commit();
session1.close();
/**********第二次*********/
Session session2 = HibernateUtils.openSession();
session2.beginTransaction();
//此時發sql語句嗎?答:發,因為session1中的數據跟隨session1一起銷毀了
Customer customer3 = (Customer)session2.get(Customer.class, 1);
System.out.println(customer3);
session2.getTransaction().commit();
session2.close();
}
測試:
小結:緩存的作用,可以提高性能,減少數據庫查詢的頻率。
[補充:原則]所有通過hibernate操作(session操作)的對象都經過一級緩存。
一級緩存是無法關閉的!內置的!hibernate自己維護的!
什麼是快照?
答:快照,是數據在內存中的副本,是數據庫中數據在內存中的映射。
如:
一句話:
快照跟數據庫數據保持一致
快照的作用就是用來更新數據的。
采用快照技術進行更新,不需要手動的調用update 方法,完全是自動的發出update語句。
保正一級緩存、數據庫、快照的一致性
【注意】
一級緩存的快照是由Hibernate來維護的,用戶可以更改一級緩存(PO對象的屬性值),但無法手動更改快照!
快照的主要能力(作用)是:用來更新數據!當刷出緩存的時候,如果一級緩存和快照不一致,則更新數據庫數據。
【示例】
通過改變查詢出來的PO的屬性值,來查看一級緩存的更改;通過提交事務,來使用快照更新數據。
@Test
public void testSnapShot(){
/**
* 證明快照的能力:自動更新數據
* 1 從數據庫查找一個對象,改變對象的某個值,手動的flush,看控制台是否發出sql語句,
* 如果控制台發出update語句,就可以表明快照的能力
*/
Session session = HibernateUtils.openSession();
session.beginTransaction();
//get的時候,不光將數據放入一級緩存,還同時將數據同步到了快照中
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer);
//修改customer對象的值
customer.setName("lucy");
//這個值是改變過後的值,是內存中的值
System.out.println(customer);
//提交事務
//如果不手動flush,在事務commit的時候,會先flush,在commit
session.getTransaction().commit();
session.close();
}
【能力擴展】快照可以用來更新數據,而且,可以用來更新部分數據。
【問題】update也是更新數據,快照也是更新數據?兩個有什麼區別?
Update更新的時候,會將所有值都更新,如果有某個屬性沒有賦值,值將會被置空
快照符合我們修改的要求:先查後改
什麼叫刷出緩存?
Session能夠在某些時間點,按照緩存中對象的變化來執行相關的SQL語句,來同步更新數據庫,這一過程被成為刷出緩存(flush)。
通俗的說法:將一級緩存的數據同步到數據庫,就是刷出緩存
什麼情況下session會執行 flush操作?
刷新緩存的三個時機:
【補充理解】:
關於Hibernate如何識別同一個對象?
根據OID,
問題:假如先查詢出來一個對象oid是1001,數據庫主鍵也是1001,但其他字段的屬性不一樣,那麼,再次查詢數據庫的數據出來的對象,和原來的對象是否是一個對象?
答案:是!
【示例】
1 、通過commit 的方式隱式的刷出緩存(證明略)
2 、通過flush的方式手動的刷出緩存
//采用flush的方式手動的刷出緩存
@Test
public void testflushcache2()
{
Session session = HibernateUtils.openSession();
// 開啟 事務
session.beginTransaction();
//獲取數據
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer);
customer.setName("rose");
//手動的flush,發出update語句,更新數據庫,並且同時更新快照
session.flush();
session.getTransaction().commit();
//特點:在數據庫中存在對應的記錄,有OID,但是不受session管理
session.close();
}
3 、使用Query的時候,(不包含get、laod:原因:get和load的處理方式,是直接獲取緩存的數據,即使一級緩存和快照的數據不一致)
會去比較一級緩存和快照是否一致,如果一致,他直接去查詢(1條select語句)
當不一致了,會先發出update語句,更新數據庫,然後在查詢(1條update語句,1條select語句)
3.1 測試get和load 的處理方式
@Test
public void testGetAndLoad_Cache(){
/**
* 證明get和load優先從緩存取數據,哪怕一級緩存和快照的數據不一致,
* 它也是直接取緩存數據
* 證明思路:
* 第一步,將數據放入一級緩存和快照
* 第二步,取 ,觀察是否發出sql語句和數據
* 第三步,改
* 第四部,取 ,觀察是否發出sql語句和數據
*
*/
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1 取,並且放入一級緩存和快照
Customer customer1 = (Customer) session.get(Customer.class, 1);
System.out.println(customer1);
//2 取:肯定不發sql語句,直接從緩存取
Customer customer2 = (Customer) session.get(Customer.class, 1);
System.out.println(customer2);
//3 改:
customer2.setName("tom");
//4 取,雖然此時一級緩存和快照不一致,但是get/load也是直接抓取緩存數據
Customer customer4 = (Customer) session.get(Customer.class, 1);
System.out.println(customer4);
//隱式flush
session.getTransaction().commit();
session.close();
}
3.2 測試query的工作方式:
【證明1】query對象不走一級緩存
//query不走一級緩存
@Test
public void testQuery_cache(){
// 證明query不使用session緩存的數據,即使緩存中有,它也會發出sql語句,查詢數據庫
Session session = HibernateUtils.openSession();
session.beginTransaction();
// /此處也會發出sql語句
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer);
//此時必須發出sql語句,因為它不會直接從一級緩存中拿數據
Customer customer2 = (Customer)session.createQuery("from Customer where id = 1").uniqueResult();
System.out.println(customer2);
session.getTransaction().commit();
session.close();
}
【證明2】但是Query對象在查詢數據的時候,會去校驗一級緩存和快照的數據是否一致,
如果不一致,發出update語句,更新數據庫,然後再發出sql查詢語句
//證明2:query對象雖然不從一級緩存取數據,但是在它去數據庫查找數據之前,會干這麼一件事情:
// 會比較一級緩存和快照是否一致,
// 如果一致,直接去數據庫查找需要的數據
// 如果不一致,先flush(先發出update語句),再去數據庫查找需要的數據
@Test
public void testQuery2(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer1 = (Customer) session.get(Customer.class, 1);
System.out.println(customer1);
customer1.setName("tom");
Customer customer2 = (Customer) session.createQuery("from Customer where id = 1").uniqueResult();
System.out.println(customer2);
session.getTransaction().commit();
session.close();
}
【提示】
flush和commit的區別:
問題:能否改變一級緩存的刷出時機?答案是可以的.
【示例】
通過在session上設置手動flush模式測試:只能通過flush刷出緩存
@Test
public void testFlushMode(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1 第一步證明:先不設置FlushMode,看運行結果
Customer customer = (Customer)session.get(Customer.class, 1);
//改變緩存中對象的屬性
customer.setName("lucy");
// 2 第二證明:設置FlushMode,看運行結果
//這個一旦設置,只有手動flush的時候,才會發出update語句
session.setFlushMode(FlushMode.MANUAL);
//第二種方式,手動flush,發出update語句
session.flush();
//在第一種情況下,此處必然發出update語句
//在第二種情況下,此處必然不會發出update語句
session.getTransaction().commit();
session.close();
}
操作一級緩存中的對象
一級緩存除了可以flush之外,還可以清除(clear,evict)、重載(refresh)。
作用:當一級緩存發生變化時,即和快照不同時,刷出一級緩存,會自動向數據庫提交update語句。
作用:清除一級緩存中的所有對象,這些對象被清除後,會從持久態對象變成脫管態.
【擴展理解】
持久態對象與session關聯的另一層含義就是對象在一級緩存中存在。
作用:清除一級緩存中的指定對象,使對象變成脫管態的。
通俗的講:不管內存中對象是否發生了更新,重新將數據庫中的內容加載到緩存中,覆蓋原來的值
@Test
public void testClearAndEvict(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
//此時數據會被放入一級緩存
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer);
//在第二次獲取之前插入代碼:clear 一級緩存
//clear:清除一級緩存中的所有數據
// session.clear();
//evict:清除一級緩存的指定對象,主要清除的是OID=1的這個對象
session.evict(customer);
//由於第一次get的時候,已經發出了sql語句查詢數據庫,所以,第二次get的時候就不會發sql語句
//如果我們執行了session.clear()代碼,表示一級緩存數據被清空了,那麼這次獲取的時候
//還是要繼續發出sql語句的
Customer customer2 = (Customer)session.get(Customer.class, 1);
System.out.println(customer2);
session.getTransaction().commit();
session.close();
}
//refresh: 不管緩存中的數據是否發生了改變,將數據庫中的數據重新放入緩存
//如果緩存的數據發生了改變,那麼這個改變將會被覆蓋
@Test
public void testRefresh(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = (Customer)session.get(Customer.class, 1);
customer.setName("rose");
System.out.println(customer);
//從數據庫中根據OID重新加載這條記錄,原先的改變失效,數據庫中原始的值會覆蓋修改的值
session.refresh(customer);
System.out.println(customer);
session.getTransaction().commit();
session.close();
}
【面試題】請你說說session的flush和refresh的區別?
為什麼要清除一級緩存:
大批量處理數據的時候,有些數據無需在一級緩存存在,或者已經處理完了,為了防止內存洩漏,一級緩存爆滿,可以手動清除一級緩存的對象。
【狀態變化】直接拿到持久態對象。
【理解 session的get方法和load方法區別】
兩者的區別:
get()方法是立即加載,即執行get方法後,立即發出查詢語句進行查詢,直接返回目標對象。
load()方法是延遲加載,即執行load方法後,不會立即發出查詢語句,返回具有id的目標對象代理類子對象,再訪問對象的除了ID之外的其他屬性的時候,才發出SQL語句進行查詢。
【示例】
分別用get和load,來根據不同id查詢不同對象,使用debug來查看語句發出的時機以及延遲加載時的代理類子對象。
@Test
public void testGetAndLoad(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
//比較get和load的區別
//1 觀察get和load的sql語句發出時機
//get方法立即發出sql語句
// Customer customer = (Customer) session.get(Customer.class, 1);
// System.out.println(customer);
//load方法:屬於懶加載,如果只用到id屬性,是不會發出sql查詢語句的
//只有用到id以外的其他屬性的時候,才會發出sql查詢語句
Customer customer = (Customer) session.load(Customer.class, 1);
//簡單的打印id,發出sql語句嗎?
System.out.println(customer.getId());
//發出sql語句嗎?
System.out.println(customer.getName());
session.getTransaction().commit();
session.close();
}
C2引用的Customer的代理子對象
繼續調試
初始化後,內部handler中initialized變為true (已經初始化), target 指向真正查詢結果對象
【關於load延遲加載的幾個情況提示】
【示例】
延遲加載時,訪問ID屬性不發出sql,訪問ID之外的屬性時發出sql
//load:不會立即發出sql,只有在訪問除id之外的其他屬性的時候,才發出sql
Customer customer2 =(Customer) session.load(Customer.class, 2);
System.out.println(customer2.getId());//不需要發出sql
System.out.println(customer2.getName());//此時才發出sql
【示例】
兩者查不到數據的區別:
結論:get會返回null,load會報錯:
【擴展了解】
代理子對象是誰來負責生成的?
答:通過javassist.jar來進行生成子對象(反射機制)
延遲加載的好處:節約資源,需要的時候再加載(提高內存利用率),如果不需要的話,先不加載。
多對多: 學生選課 (一個學生選多門課, 一門課被多個學生選擇)
一對多: 客戶和訂單 (一個客戶可以產生很多訂單, 一個訂單屬於一個客戶)
一對一: 一個公司對應建表規則:
多對多: 一定產生第三方關系表, 需要三張表 (學生表、 課程表、 選課表), 關系表聯合主鍵,引入兩張實體表主鍵,作為外鍵
一對多: 在多方表,添加一方主鍵作為外鍵 ,不需要第三張表 , 需兩張表(客戶表 、訂單表),在訂單表添加客戶id
一對一: 在任意一方添加對方主鍵作為外鍵
一個地址
范式:可以理解成是數據庫設計的規范/標准
為什麼表的設計需要有規范和標准?
防止數據冗余,科學的數據庫設計可以防止數據冗余
//一般數據庫的設計要符合3NF,符合的范式要求越高,表越多
范式之間的關系:
第一范式(1NF):保證每列的原子性(每列都是不可再分割的單元)
學生表
stuno
stuinfo
Coursename
1
Lucy23
Java
1
Tom18
Oracle
1
Rose12
Hibernate
上表是不符合第一范式的,修改如下
stuno
Name
Age
Coursename
1
Lucy
23
Java
1
Tom
18
Oracle
1
Rose
12
Hibernate
第二范式:保證表有主鍵
上表符合第二范式嗎?不符合,因為沒有主鍵,修改如下:
stuno
Name
Age
Coursename
1
Lucy
23
Java
2
Tom
18
Oracle
3
Rose
12
Hibernate
第三范式:每張表不包含其他表中非主鍵以外的字段(每張表的字段都依賴於當前的主鍵)
上表符合第三范式嗎?答:不符合,修改如下
stuno
Name
Age
1
Lucy
23
2
Tom
18
3
Rose
12
Courseid
Coursename
1
Java
2
Oracle
3
Hibernate
關系表:
Stuno
Courseid
1
1
1
2
2
1
2
3
BCNF
4NF
5NF
Hibernate 是一個完全ORM框架,使用hibernate 編程,可以完成類和表映射
多對多 :
Student {
// 一個學生對應 多門課
// Set、List、bag、數組 代表復數,基本區別:set不能重復,沒順序、list可以重復,有順序。bag:不能重復,而且有順序(缺點:效率低)
Set<Cource> cources ;
}
Cource {
// 一門課,多個學生
Set<Student> students ;
}
一對多 :
Customer {
// 一個客戶 多個訂單
Set<Order> orders ;
}
Order {
// 一個訂單 一個客戶
Customer customer ;
}
一對一:
Company {
// 一個公司 一個地址
Address address;
}
Address {
// 一個地址 對應一個公司
Company company ;
}
我們下面重點學習一對多和多對多!
案例:客戶和訂單(一對多)
建立一個包用於測試: cn.itcast.b_oneToMany
編寫方法:實體類的編寫,先寫單表的屬性和配置,再加關系。
【第一步】:實體類編寫
【第二步】:hbm映射文件編寫
Order.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.b_onetomany.Order" table="t_order">
<!-- 主鍵 -->
<id name="id">
<generator class="native"></generator>
</id>
<!-- 其他屬性 -->
<property name="name"></property>
<!-- 配置關系
name:類中屬性
class:這個屬性的原型(屬性對應對象的完整的包路徑)
column:customer在Order表中的外鍵的名字
-->
<many-to-one name="customer" class="cn.itcast.b_onetomany.Customer" column="cid"></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- class -->
<class name="cn.itcast.b_onetomany.Customer" table="t_customer">
<!-- 配置主鍵 -->
<id name="id">
<!-- 主鍵策略 -->
<generator class="native"></generator>
</id>
<!-- 其他屬性 -->
<property name="name"></property>
<!-- 配置集合
name:類中的屬性名
-->
<set name="orders">
<!-- 配置當前Customer對象在order表中的外鍵的名字 -->
<key column="cid"></key>
<!-- 配置關系
class:當前order對應的完整的包路徑(集合中裝載的數據的原型)
-->
<one-to-many class="cn.itcast.b_onetomany.Order"/>
</set>
</class>
</hibernate-mapping>
注意:兩個配置文件的外鍵必須對應!!!!!
【第三步】:核心配置文件中添加映射
<!-- 配置一對多的映射文件 -->
<mapping resource="cn/itcast/b_onetomany/Customer.hbm.xml"/>
<mapping resource="cn/itcast/b_onetomany/Order.hbm.xml"/>
【第四步】:建表測試
@Test
public void createTable()
{
HibernateUtils.getSessionFactory();
}
建表成功:
提示:外鍵的數據類型自動會使用對方主鍵的類型
本節難點:cascade級聯和inverse外鍵維護
多表保存的原則:雙方都必須是持久態的對象!
目標:學習幾種保存方法。
@Test
public void testSave(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = new Customer();
c1.setName("rose");
Order o1 = new Order();
o1.setName(c1.getName()+"的訂單1");
//雙向建立關系
c1.getOrders().add(o1);
o1.setCustomer(c1);
//必須同時保存兩個對象,否則會報錯
session.save(c1);
session.save(o1);
session.getTransaction().commit();
session.close();
}
這種保存要求:必須雙方都建立關系,而且都要執行保存操作.
【需求】
保存客戶的同時自動保存訂單。
默認情況下:
會報錯:
原因結論: 在hibernate代碼中,在session.flush前,不允許 持久態對象 關聯 瞬時態對象
持久態對象只能關聯持久態對象!
解決:采用級聯 ,cascade :cascade="save-update"
它的作用:
可以使持久態對象"關聯"瞬時態對象, 自動會隱式執行save操作,變為持久態,
可以使持久態對象"關聯"脫管對象,自動會隱式執行update操作,變為持久態
如果通過操作customer來級聯保存 order ,需要在Customer.hbm.xml(誰是持久的) 配置級聯.
Customer.hbm.xml:
測試代碼:
@Test
public void testSave(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = new Customer();
c1.setName("lucy");
Order o1 = new Order();
o1.setName(c1.getName()+"的訂單1");
Order o2= new Order();
o2.setName(c1.getName()+"的訂單2");
Order o3 = new Order();
o3.setName(c1.getName()+"的訂單3");
Order o4 = new Order();
o4.setName(c1.getName()+"的訂單4");
//當級聯保存的時候,當我們在Customer.hbm.xml中設置了級聯關系的時候,
//那麼在設置關系的時候,只需要向Customer的orders集合中添加Order,就可以進行級聯保存
c1.getOrders().add(o1);
c1.getOrders().add(o2);
c1.getOrders().add(o3);
c1.getOrders().add(o4);
// o1.setCustomer(c1);
//必須同時保存兩個對象,否則會報錯
session.save(c1);
// session.save(o1);
session.getTransaction().commit();
session.close();
}
使用級聯之後,Hibernate會對瞬時態的這個對象,會自動執行save操作.
問題:如果保存順序反過來呢?即先保存訂單,同時保存客戶呢?
分析:先操作order ,級聯保存 customer ,需要在 Order.hbm.xml 配置級聯
Order.hbm.xml :
//保存訂單的時候,能不能自動的保存customer
@Test
public void testSave3(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = new Customer();
c1.setName("jack");
Order o1 = new Order();
o1.setName(c1.getName()+"的訂單1");
o1.setCustomer(c1);
session.save(o1);
session.getTransaction().commit();
session.close();
}
級聯對於大量的保存或更新操作非常有用。
下面有個面試題,前提:customer和order都配置了級聯保存(雙向都配置),那麼請問下面的1,2,3語句分別產生幾條插入的sql語句,
答案: 4 3 1
@Test
public void testSaveByCascadeNavi(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c1 = new Customer();
c1.setName("itcast2");
Order o1 = new Order();
o1.setName(c1.getName()+"的訂單1");
Order o2 = new Order();
o2.setName(c1.getName()+"的訂單1");
Order o3 = new Order();
o3.setName(c1.getName()+"的訂單1");
o1.setCustomer(c1);
c1.getOrders().add(o2);
c1.getOrders().add(o3);
//第一種情況 4
session.save(o1);
//第2種情況 3
session.save(c1);
//第3種情況 1
session.save(o2);
session.getTransaction().commit();
session.close();
}
Hibernate的外鍵:是由關系來提供
導航保存對於大量的保存或更新操作非常有用。
表之間的依賴關系:在一對多中 ,多方表(從表) 依賴 一方表(主表) (order依賴customer)。
// 第一種情況:直接刪除多方的數據
// Order order = new Order();
// order.setId(7);
//
// session.delete(order);
刪除多方 (訂單),直接刪除
//第二種情況:直接刪除一方的數據
Customer customer = new Customer();
customer.setId(4);
//當customer是一個脫管態對象的時候,先解除關系,刪除,所以刪除之後,你會發現多方的外鍵被置空
session.delete(customer);
刪除一方(客戶),被依賴
結果:多方的外鍵被置空了,一方被刪除了(內部機制)
原理:Hibernate 先解除對一方外鍵依賴,然後進行刪除
(如果外鍵設置 not-null , 無法刪除 )課後可以試試
問題: 從關系型數據庫的角度來說:在一對多數據模型中,多方對一方存在依賴的, 如果一方數據被刪除,多方數據不完整,無意義。(客戶刪除的同時,將訂單一塊都刪了)
解決方法: 在一方 配置 cascade="delete" ,會級聯刪除.
級聯刪除的要求:被刪除的對象要是持久態的對象
@Test
public void testDelete(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
//刪除多方的數據:直接刪除
// Order order = new Order();
// order.setId(15);//設置oid,刪除只能根據id刪除
// 刪除
// session.delete(order);
//刪除一方的數據
// Customer customer = new Customer();
// customer.setId(7);
//當刪除的時候,hibernate會先解除關系(將cid置空),然後在進行一方的刪除操作
// session.delete(customer);
/**
* 在進行級聯刪除的時候,如果刪除的對象還是脫管態對象,級聯刪除失效,
* 默認處理方式:先解除關系,再一方刪除數據(多方數據還存在,並沒有達到級聯刪除的效果)
*
* 級聯刪除:一方對象必須是持久態,這樣子,才能實現級聯刪除的效果
*/
//持久態
Customer customer = (Customer)session.get(Customer.class, 6);
//由於customer對象是持久態,所以會級聯刪除order訂單
session.delete(customer);
session.getTransaction().commit();
session.close();
}
結論:刪除操作中,刪除托管對象沒有級聯效果,刪除持久對象可以進行級聯刪除
Hibernate級聯開發配置, cascade常用的取值:
在實際開發中,一對多模型中,一方一般是主動的一方(多方要依賴一方),如果配置級聯,通常在一方進行配置!!!
因為多方需要引用一方的主鍵作為外鍵使用
級聯刪除的情況:刪除客戶,訂單也已經沒有存在的意義了
刪除訂單,沒有必要刪除客戶,沒有必要再多方配刪除級聯
實際項目開發中,一般級聯主要是用來進行級聯刪除操作,很少用來進行級聯保存。一般都是先有一方的數據,再有多方的數據,即先有客戶,再有訂單。所以,保存多方不配置級聯(不在多方配置級聯)。
總結:
一般在業務開發中,不要兩端都配置級聯,(多方盡量不要配置級聯,盡量在一方配置級聯)
配置了級聯之後,必須操作持久態對象,否則不會級聯刪除。
問題:多余sql的問題
示例:
將沒有關系的一個客戶和一個訂單建立關系。(雙方)
@Test
public void testInverse(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 獲取5號客戶
Customer customer = (Customer)session.get(Customer.class, 5);
// 獲取訂單
Order order = (Order)session.get(Order.class, 11);
//添加雙向關聯關系
customer.getOrders().add(order);
order.setCustomer(customer);
//此時當commit的時候,會隱式的flush
session.getTransaction().commit();
session.close();
}
產生的sql:多余sql
分析圖:
解決方法:
采用inverse屬性來配置。
簡單的說這個屬性誰是true,就放棄了主鍵維護權。
inverse默認值是false,即雙方都有外鍵維護權。
inverse只對集合起作用,也就是只對one-to-many或many-to-many有效.
在業務開發中,一般是在一方放棄。(從業務場景上來分析,一般先存1 方,再存多方,那麼就存多方的時候建立關系就比較合理。)
再次運行,發現就一條語句了:
一般,我們都讓1方放棄外鍵維護權!
學習點:掌握多對多配置方式。
案例:學生和課程,學生選課
學生和課程 是經典 多對多關系,學生選課是關系表數據
在多對多中,一般情況 沒必要使用 cascade 級聯的!!!
【第一步】:實體類編寫,創建cn.itcast.c_manytomany包,然後操作如下:
【第二步】:hbm映射文件編寫
student.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.c_manytomany.Student" table="t_student">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<!-- 配置集合
name:類中的屬性
table:關系表名
-->
<set name="courses" table="t_s_c">
<!-- 當前Student對象在關系表中的外鍵名字 -->
<key column="sid"></key>
<!-- 配置關系
class:當前的Course對應的完整的包路徑
當前集合中裝入的對象對應的完整的包路徑
column:當前的Course對象在關系表中的外鍵
-->
<many-to-many class="cn.itcast.c_manytomany.Course" column="cid"></many-to-many>
</set>
</class>
</hibernate-mapping>
Course.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.c_manytomany.Course" table="t_course">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<!-- 配置集合
table:配置多對多關系的時候,兩邊的table要一致
-->
<set name="stus" table="t_s_c">
<key column="cid"></key>
<many-to-many class="cn.itcast.c_manytomany.Student" column="sid"></many-to-many>
</set>
</class>
</hibernate-mapping>
【第三步】:核心配置中添加映射
【第四步】:建表測試
建表完成之後,查看建表語句:
圖解:
只要是多對一的標簽,都配置自己在對方的外鍵屬性。
即保存學生和課程數據的同時,在中間選課表插入關聯數據。
@Test
public void testSave()
{
Session session = HibernateUtils.openSession();
session.beginTransaction();
//新建一個學生
Student stu = new Student();
stu.setName("rose");
//新建課程
Course course = new Course();
course.setName("hibernate");
// /雙向關聯
stu.getCourses().add(course);
course.getStus().add(stu);
//發現拋出異常,如何解決
//1.外鍵維護權,一方放棄inverse="true",並且不放棄維護權的一方,加入 cascade="save-update"
//2.建立關系是,只需要建立一方的關系即可,並且建立關系的一方,加入 cascade="save-update"
//雙向保存
session.save(course);
session.save(stu);
session.getTransaction().commit();
session.close();
}
錯誤:
可以采用兩種方案解決問題:
第一種方案.外鍵維護權,一方放棄inverse="true",並且不放棄維護權的一方,加入 cascade="save-update":推薦方案
第二種方案.建立關系時,只需要建立一方的關系即可,並且建立關系的一方,加入 cascade="save-update"
第一種方案:
執行的代碼:
@Test
public void testSave1(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
//創建學生
Student s1 = new Student();
s1.setName("rose");
///創建課程
Course c1 = new Course();
c1.setName("hibernate");
//建立雙向關聯關系
s1.getCourses().add(c1);
c1.getStus().add(s1);
//保存
// session.save(c1);
// session.save(s1);
//第一種方案:一方放棄外鍵維護權,另一方加入級聯保存,未來保存的時候,在另一方進行保存操作
session.save(s1);
session.getTransaction().commit();
session.close();
}
第二種方案:第二種方案xml文件的配置直接采用第一種方案的配置,不做任何修改,此時,我們知道Student.hbm.xml中配置了
Cascade="save-update",所以接下來的測試代碼如下:
實際業務中,一般不會去級聯保存,因為學生和課程是各自產生存在的.
結論:多對多模型中,一次創建關系,對應中間表 一條insert語句 !不需要雙方發生關系。
在實際業務中,要麼是在一方建立關系,要麼是如果兩方都建立關系,就配置inverse,讓一方主動放棄維護權。
我們推薦一方放棄維護權
兩個對象解除關系就是刪除中間表的關系數據,即將兩個對象解除關系。刪除的時候,對象必須是持久態對象
@Test
public void deleteRelation(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Student student = (Student) session.get(Student.class, 3);
Course course = (Course) session.get(Course.class, 3);
//注意:由於course放棄了對集合的維護權,所以此時只能在Student這一方進行集合操作
student.getCourses().remove(course);
//不需要手動的刪除,直接使用快照的更新功能,commit會隱式的flush
session.getTransaction().commit();
session.close();
}
語句問題:查看語句。
變更選課內容,原來選的語文,改為選數學
----- hibernate 無法生成update語句 (只能由我們自己先delete,後insert—先解除關系,再增加關系 )
分析:假如能update,你要update中間表,但中間表無法通過實體操作。
插入測試數據,-
@Test
public void changeRelation(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
//讓2號學生選修3號課程
Student student = (Student) session.get(Student.class, 2);
Course course = (Course) session.get(Course.class, 2);
//解除關系
student.getCourses().remove(course);
//查找3號課程
Course course2 = (Course) session.get(Course.class, 3);
//添加關系
student.getCourses().add(course2);
//flush
session.getTransaction().commit();
session.close();
}
多對多的刪除!!!!!!!!!!
一定不能級聯刪除!!!!!!!
刪除學生的時候,Hibernate會自動刪除關系表(中間表)的數據(無需級聯)
@Test
public void testDeleteEntity(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Student stu = (Student)session.get(Student.class, 2);
//刪除
session.delete(stu);
//flush
session.getTransaction().commit();
session.close();
}
注意: 多對多不建議使用 級聯 (delete 級聯),造成數據丟失 !!!!!!!(如果兩邊都配置級聯,會兩邊刪除,造成表被清空)
測試一下:(實際業務中是不存在的!!3個學生對應3個課程,創建9個關系,隨便刪除哪個對象,數據直接被置空)
在課程或者學生方配置級聯
如果在學生方面配置了級聯,那麼當刪除持久態的學生對象時,會將對應的課程也同時刪除掉了!從而造成數據丟失!(刪除脫管的不會,因為脫管態對級聯不起作用)
(課後嘗試多對多的級聯刪除)
復習:
1 能夠說出PO對象三狀態
2 理解session一級緩存(能夠存儲數據)
3 理解快照(用來更新數據的)
4 get和load的區別
5 掌握一對多的配置
6 掌握一對多的CRUD操作
7 掌握多對多的配置
8 掌握對多對的CRUD操作
學會debug,多用debug
【作業一】
完成全天課程練習。
【作業二】
完成課前資料中的:《hibernate知識點作業練習》文檔中的練習。