Hibernate3 第二天 第一天回顧: 三個準備 創建資料庫 準備po和hbm文件 準備靈魂文件hibernate.cfg.xml 七個步驟 1 載入配置文件Configuration 2 創建會話工廠SessionFactory 3 獲取連接Session 4 開啟事務Transaction ... ...
Hibernate3 第二天
第一天回顧:
- 三個準備
- 創建資料庫
- 準備po和hbm文件
- 準備靈魂文件hibernate.cfg.xml
- 七個步驟
- 1 載入配置文件Configuration
- 2 創建會話工廠SessionFactory
- 3 獲取連接Session
- 4 開啟事務Transaction
- 5 各種操作
- 6 提交事務commit
- 7 關閉連接close
今天內容安排:
- Hibernate的持久化對象(PO)相關狀態和操作。(重點理解)
- Hibernate持久化對象的狀態(3個)和轉換。
- Session的一級緩存。
- Session一級緩存的快照(snapshot)。
- 多表關聯映射配置和操作(重點應用)
- 一對多關聯映射配置和操作、以及級聯配置、外鍵維護配置。
- 多對多關聯映射配置和操作。
學習目標:
- 掌握Hibernate的核心概念:PO的狀態+一級緩存和快照
- 掌握多表映射的配置、級聯配置和增刪改的操作(項目中使用)
- 逐步學會和習慣使用debug
Hibernate的持久化對象相關概念和操作
Hibernate持久化對象(po)的狀態和轉換
持久化對象的狀態
官方描述:
Hibernate 將操作的PO對象分為三種狀態:
- 瞬時 (Transient )/ 臨時: 通常new 創建對象(持久化類),未與Session關聯,沒有OID
- 持久 (Persistent) : 在資料庫存在對應實例,擁有持久化標識OID, 與Session關聯(受session管理)
- 脫管 (Detached)/游離:當Session關閉後,持久狀態對象與Session斷開關聯,稱為脫管對象,此時也持有OID
搭建測試環境:創建項目Hibernate3_day02
- 導入開發jar包(11個),將hibernate.cfg.xml、log4j.properties複製到src,修改Hibernate.cfg.xml的jdbc連接參數,導入hibernateUtils工具類。
- 創建包:cn.itcast.a_postate,在包中創建Customer類,代碼如下:
- 編寫hbm映射
<?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> |
- 在hibernate.cfg.xml載入映射配置
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,因此,只要與session關聯的就是持久態。
瞬時態和脫管態:相同點是都和Session沒關聯,不同點是瞬時態沒有OID,而脫管態有OID。
【思考】
通過上述的分析,發現,瞬時態和脫管態對象就差一個OID,那麼瞬時態的對象中給主鍵ID屬性賦值後就是脫管態了麽?
未必!
首先,需要區分持久化標識OID和對象中主鍵ID屬性的關係:
- 在持久化之前,雖然有ID屬性,但資料庫中沒有對應的數據,那麼此時OID是null;
- 在持久化之後,這個ID屬性值被插入資料庫中當主鍵了,資料庫中有對應的數據了,此時OID就有值了,而且與主鍵值保持一致性,比如類型、長度等。
因此:OID和PO對象中主鍵ID屬性的區別就是:資料庫存在不存在,如果存在就是OID,如果不存在,那就是個ID屬性而已。
瞬時態和脫管態的區別總結:
- 脫管態對象:有持久化的標識oid,並且在資料庫中存在。
- 瞬時態對象:無持久化標識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一致,它就直接認為是同一個對象。
Session的一級緩存(重點理解)
什麼是一級緩存?
又稱為: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自己維護的!
Session一級緩存的快照
什麼是一級緩存的快照(snapshot)
什麼是快照?
答:快照,是數據在記憶體中的副本,是資料庫中數據在記憶體中的映射。
如:
一句話:
快照跟資料庫數據保持一致
快照的作用就是用來更新數據的。
一級緩存快照的原理(圖)
採用快照技術進行更新,不需要手動的調用update 方法,完全是自動的發出update語句。
保正一級緩存、資料庫、快照的一致性
【註意】
- 持久態對象原則:po對象儘量保持與資料庫一一致。
- 當一級緩存和快照不一致的時候,會先發出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操作?
刷新緩存的三個時機:
- 事務提交commit():該方法先刷出緩存(session.flush()),然後再向資料庫提交事務。
- 手動刷出flush():直接調用session.flush()。
- 查詢操作:當Query查詢(get、load除外,這兩個會優先從一級緩存獲取數據)時,會去比較一級緩存和快照,如果數據一致,則去資料庫直接獲取數據,如果緩存中持久化對象的屬性已經發生了變化,(一級緩存和快照不一樣了),則先刷出緩存,發出update語句,然後查詢,以保證查詢結果能夠反映出持久化對象的最新狀態。(Query查詢數據不走一級緩存)
【補充理解】:
關於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 的處理方式