Hibernate技術

Hibernate技術

不懂技術的人或者技術新手往往容易被框架二字所唬住,所謂框架是前人對相關問題處理方案的總結,將對某類問題最有價值的解決方式彙集在一起,形成框架。其它人使用時,僅僅只需要按照框架締結者設定的規則以及調用的API,來完成對框架的使用。

學習hibernate,學習的主要是hibernate的使用規則,理解這個框架的思想。

1ConfigurationSessionFactorySession三個類一個都不能少,通通都需要了解。所謂瞭解,其實是誇大了,不少人僅僅只是使用這三個類最簡單的創建過程代碼,但這已經足夠應付絕大多數場景了。無論你使用的時hibernate.properties,還是hibernate.cfg.xml,抑或者你自定義了一個配置文件,確保自己配置的正確性。

2、必須學習hbm.xml文件的編寫規則。新手可以依靠一些自動生成工具來完成對hbm.xml以及java文件的生成,但上手便這樣,不利於學習。工具雖然方便,但是掩蓋了生成時所應該知道的基本原理。

3hibernate的三種查詢方式:HQLCriteriaNative SQL。每種技術有其應用的優勢場景,技術不分優劣,只有最適合當前場景使用的。HQL是使用最頻繁的,Criteria是完全OO的,當你需要使用特定數據庫的特性時,Native SQL是首選。

4、關聯關係。數據庫中的多表關聯本是平常事,但這個問題又恰恰是最容易讓人暈頭轉向的地方。hibernate處理關聯關係的精髓內容在hbm.xml中,學習時(1)注意關聯行爲的主被動方(2)弄清關聯的對象所依據的字段。

5、優化。Hibernate在封裝現有JDBC操作的同時,對數據庫操作進行了默認的一些優化。而通過延遲加載與批量操作等相關參數設置,我們可以進一步對數據庫操作的性能進行優化。

Hibernate學習總結

1 Hibernate概述
Hibernate
是一個持久層框架,用來負責實現對象和關係型數據庫的轉換。2003Hibernate 2發佈,2005Hibernate 3發佈。現在已經成爲最爲流行的ORMObject/Relational Mapper)框架。
2 Hibernate
內容
2.1 Hibernate
的三種狀態
 A. Transient
(臨時對象)
 new
出來的對象,此狀態下的對象還沒有與數據庫的記錄對應上。或者通過session.delete(obj)操作的obj對象將從Detached狀態改變爲Transient狀態
 B. Persistent
(持久對象)
 
--Transient狀態的對象使用Sessionsave()方法保存到數據庫後,對象成爲persistent狀態,例:
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) session.save(fritz);
--使用Hibernate從數據庫得到數據並封裝爲對象(例如使用get()load()),則該對象爲Persistent狀態;
>>get()
User user = (User) session.get(User.class, new Long(userID));
>>load()
User user = (User) session.load(User.class, new Long(userID));
get()
load()的區別在於:當要查找的對象數據不存在時,load()方法就是直接拋出異常,而get()方法則返回null
--Detached 狀態對象重新和session關聯後(通過updatelock方法)變成Persistent 狀態,例:
>>update()
//user
session1關閉後留下的未關聯對象
user.setPassword("secret");
Session session2 = sessionFactory.openSession();
Transaction tx = session2.beginTransaction();
session2.update(user); //
此時userDetached狀態轉爲Persistent狀態(sessionuser關聯)
user.setUsername("jonny");
tx.commit();
session2.close();
這種方式,關聯前後做修改都不打緊,關聯前後做的修改都會被更新到數據庫;
比如關聯前你修改了password,關聯後修改了username,事務提交時執行的update語句會把passwordusername都更新
>>lock()
Session session2 = sessions.openSession();
Transaction tx = session2 .beginTransaction();
session2 .lock(user, LockMode.NONE);
user.setPassword("secret");
user.setLoginName("jonny");
tx.commit();
session2 .close();
這種方式,關聯前後是否做修改很重要,關聯前做的修改不會被更新到數據庫,
比如關聯前你修改了password,關聯後修改了loginname,事務提交時執行的update語句只會把loginname更新到數據庫
所以,確信未關聯對象沒有做過更改才能使用lock()

Ps: 如果將Session實例關閉,則Persistent狀態的對象會成爲Detached狀態。
 C. Detached
(未關聯對象)
Detached
狀態的對象,與數據庫中的具體數據對應,但脫離Session實例的管理,例如:
在使用load()get()方法查詢到數據並封裝爲對象之後,將Session實例關閉,則對象由Persistent狀態變爲Detached狀態。
Detached
狀態的對象之任何屬性變動,不會對數據庫中的數據造成任何的影響。
這種狀態的對象相當於cache數據,因爲他不和session關聯,誰都可以用,任何session都可以用它,用完後再放到cache中。
2.2 VO
PO
 
Transient狀態和Detached狀態的對象統稱爲VOValue Object)對象,而將Persistent狀態的對象稱爲POPersistence Object)對象。
 
我認爲,可以簡單的理解爲當前和session關聯的對象就是PO對象,否則爲VO對象
 
Ps:
應該儘量避免將PO對象傳入/傳出除持久層外的其他層面(如在view層修改PO)。解決辦法是構造一個新的VO,使其具備和PO相同的屬性,在系統其他層面使用VO進行數據傳輸。(但在實際應用中這種方法增加了系統複雜性並且影響性能,所以很多系統設計還是傾向於可以在view層操縱PO的)
2.3 SessionFactory
Session
SessionFactory
用來創建session對象,由於SessionFactory本身是單例實現,並且是線程安全的,所以在一個應用中只能創建一個SessionFactory實例。例:
Configuration config = new Configuration.configure();
SessionFactory sessionFactory = config.buildSessionFactory();

Session對象用SessionFactory創建,創建後可以調用其save,get,delete,find方法對持久層數據進行操作。(其中,find()方法在Hibernate3中被取消,用QueryCriteria代替)最後,通過調用session.close()方法關閉session
2.4
緩存
 
緩存種類:分爲事務級緩存,應用級緩存和分佈式緩存。但由於分佈式緩存在同步過程中會佔用大量帶寬,嚴重影響系統性能,所以不建議使用。
2.4.1
一級緩存
 
一級緩存是Hibernate的內部緩存,屬於事務級緩存。
 
它在SessionImpl中,以Map的形式實現。當要從Session中取得某個PO時,會判斷此POidClassNameMap中是否已經存在,如果已經存在,則直接從緩存中取出。
 
有兩種手動干預內部緩存的方法:
a. Session.evict
將某個特定對象從內部緩存中清楚
b. Session.clear
清空內部緩存

 當批量插入數據時,會引發內存溢出,這就是由於內部緩存造成的。例如:
 For(int i=0; i<1000000; i++){
  For(int j=0; j<1000000; j++){
   User user = new User();
   user.setUserName(“gaosong”);
   user.setPassword(“123”);
   session.save(user);  
}
}
在每次循環時,都會有一個新的對象被納入內部緩存中,所以大批量的插入數據會導致內存溢出。解決辦法有兩種:a 定量清除內部緩存 b 使用JDBC進行批量導入,繞過緩存機制。

a 定量清除內部緩存
For(int i=0; i<1000000; i++){
  For(int j=0; j<1000000; j++){
   User user = new User();
   user.setUserName(“gaosong”);
   user.setPassword(“123”);
   session.save(user);  

   if(j%50 == 0){
    session.flush();
    session.clear(); //
清除內部緩存的全部數據,及時釋放出佔用的內存
}
}
}

b 使用JDBC進行批量導入,繞過緩存機制
Transaction tx=session.beginTransaction(); //
使用Hibernate事務處理邊界Connection conn=session.connection();
PrepareStatement stmt=conn.prepareStatement(“insert into T_STUDENT(name) values(?)”);
for(int j=0;j++;j<200){
for(int i=0;i++;j<50) {
stmt.setString(1,”feifei”);
}
}
stmt.executeUpdate();
tx.commit(); //
使用 Hibernate事務處理邊界

ps:注意,這裏使用Hibernate的事務處理機制處理Connection
 2.4.2
二級緩存
2.4.2.1
概述
 Hibernate
的二級緩存涵蓋了應用級緩存和分佈式緩存領域。Hibernate默認使用EHCache作爲二級緩存的實現。(二級緩存由第三方實現)
 
緩存後,可能出現問題主要有:
1. 
髒讀:一個事務讀取了另一個並行事務未提交的數據。
2. 
不可重複讀:一個事務再次讀取之前的數據時,得到的數據不一致,被另一個已提交的事務修改。
3. 
幻想讀:一個事務重新執行一個查詢,返回的記錄中包含了因爲其他最近提交的事務而產生的新記錄。
對應以上問題,Hibernate提供了四種內置的緩存同步策略:
1. read-only
:只讀。對於不會發生改變的數據,可使用只讀型緩存。
2. nonstrict-read-write
:如果程序對併發訪問下的數據同步要求不是非常嚴格,且數據更新操作頻率較低,可以採用本選項,獲得較好的性能。
3. read-write
:嚴格可讀寫緩存。基於時間戳判定機制,實現了“read committed”事務隔離等級。可用於對數據同步要求嚴格的情況,但不支持分佈式緩存。這也是實際應用中使用最多的同步策略。
4. transactional
:事務型緩存,發生異常的時候,緩存也能夠回滾,必須運行在JTA事務環境中。實現了“Repeatable read” 事務隔離等級。

Ps: 讀寫緩存和不嚴格讀寫緩存在實現上的區別在於,讀寫緩存更新緩存的時候會把緩存裏面的數據換成一個鎖,其他事務如果去取相應的緩存數據,發現被鎖住了,然後就直接取數據庫查詢。
hibernate2.1ehcache實現中,如果鎖住部分緩存的事務發生了異常,那麼緩存會一直被鎖住,直到60秒後超時。
不嚴格讀寫緩存不鎖定緩存中的數據。
2.4.2.2

 
爲了避免髒數據的出現,Hibernate默認實現了樂觀鎖機制。原理是爲表加一個version字段來控制。例如:version初始值爲1AB兩個事務同時修改表,AB都讀入了version=1A修改完成後將version1version=2B修改完後也將version1,當試圖提交時發現:當前version已經爲2,而提交版本也爲2,違反了提交版本必須大於記錄版本的樂觀鎖策略,事務被回滾。因此避免了髒數據的出現的可能。
2.4.2.3 Class
緩存
 Class
緩存是一個Map,其中keyClassNameValue是一個id序列(其中的id就對應DB中的一條記錄,也就是對應一個PO)。當調用session.iterate(…)方法時,將通過一條查詢語句返回一個id序列,只要序列中的idMap中存在,則取出Map中此id對應的POJO
 session.iterate(…)
方法和session.find(…)方法的區別:session.find(…)方法並不讀取ClassCache,它通過查詢語句直接查詢出結果數據,並將結果數據putclassCachesession.iterate(…)方法返回id序列,根據id讀取ClassCache,如果沒有命中在去DB中查詢出對應數據。
 
當使用session.find(…)方法時,如果返回大量數據,會將很多POJO放入內存中,導致內存溢出,此時可以用limit方式限定返回數量。
 Ps:
由於session.iterate(…)方法不能規避N+1的問題,所以基本不用此方法。
2.4.2.4
查詢緩存
 Hibernate
查詢緩存根據HQL生成的SQL作爲key,對應一個查詢出的id序列,如果兩次查詢的SQL相同,將命中查詢緩存,根據id序列再到ClassCacheload出對應的POJO。但是,如果在兩次查詢中數據庫表有改動(Update/Insert/Delete),則會將查詢緩存中的數據清空。所以查詢緩存只能應用於兩次查詢中DB表沒有改變,並且是同樣的SQL查詢的情況。
 
這裏有個問題,就是查詢緩存機制可能會遇到N+1的問題。因爲當從查詢緩存中讀出id序列後,將去ClassCache中找,如果此時ClassCache中沒有id對應的POJO,則將向數據庫中發送查詢語句。所以一定要確保ClassCache中數據的生命週期要比QueryCache的長,(比如ClassCache的超時時間一定不能短於QueryCache設置的超時時間)
 2.4.2.5
批量操作(Update/Delete
 
Hibernate3.0.5以前的版本和以後的有很大差異,這裏分兩部分祥解:
1. Hibernate2.x
Hibernate2.x中,批量操作時是不推薦用Hibernate自己的update/delete方法的,這是因爲Hibernate在批量更新/刪除時必須維護ClassCache,這就導致出現N+1的情況影響性能,所以不可取。解決方法是使用JDBC的相應方法,繞開緩存。
2. Hibernate3.x
Hibernate3.x中,加入了“bulk update/delete”的操作,使得可以使用Hibernate本身的批量更新功能。
“bulk update”
只是簡單的發送一條update語句,並不維護ClassCache。此時就會有髒數據出現的可能。(需要確認)
另一方面,“bulk delete”發送一條delete語句,並且到ClassCache中將delete對應的Class設置爲無效,實際是清空了表對應的ClassCache
2.4.3 session.save
session.save
方法的執行順序:
1. 
在一級緩存中尋找POJO,如果命中則認爲此對象執行過Insert方法,直接返回。
2. 
如果實現了lifecycle接口,則調用onSave方法
3. 
如果實現了Validatable接口,則調用validate方法
4. 
調用攔截機的Interceptor.onSave方法
5. 
構造Insert Sql,並且執行
6. 
返回新插入記錄的idPOJO
7. 
POJO放入一級緩存中(session.save不會將POJO放入二級緩存,是因爲通過save保存的POJO在事務的剩餘部分被修改的可能往往很高,頻繁更改二級緩存得不償失)
8. 
處理級連關係
3 Hibernate3.2
新特性
1. Hibernate Annotation
 2. bulk update/delete
(批量更新/刪除)
 3.
lucene集成
 4.
取消Hibernate2中的session.find方法,改爲session.createQuery.list方法
 5.
修改包名 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章