Hibernate3 持久化

一、實體對象的生命週期

1,實體對象的三種狀態(生命週期中的三種狀態)
(1),Transient:在內存中的自由存在,與數據庫中的記錄無關。
public void methodA{
    TUser user = new TUser();
    user.setName("Emma");            ---user與數據庫中的記錄沒有關聯。
}

(2),Persistent:處於由Hibernate框架所管理的狀態。實體對象的引用被納入Hibernate實體容器中加以管理。
TUser user = new TUser();
TUser anotherUser = new TUser();

user.setName("Emma");
anotherUser.setName("Kevin");
//此時user和anotherUser都處於Transient狀態

Transaction tx = session.beginTransaction();
session.save(user);
//此時的user對象已經由Hibernate納入實體管理容器,處於Persistent狀態
//而anotherUser仍然處於Transient狀態。
tx.commit();
//事務提交之後,庫表中已經插入一條用戶"Emma"的記錄
//對於anotherUser則無任何操作

Transaction tx2 = session.beginTransaction();
user.setName("Emma_1"); //Persistent
anotherUser.setName("Kevin_1"); //Transient

tx2.commit();
//雖然這個事務中我們沒有顯式調用Session.save()方法保存user對象,但是由於處於
//Persistent狀態的對象將自動被固化到數據庫中,因此user對象的變化也將被同步到
//數據庫中,也就是說數據庫中"Emma"的用戶記錄已經被更新爲"Emma_1"

//此時anotherUser仍然是個普通Java對象,處於Transient狀態,它不受Hibernate
//框架管理,因此其屬性的更改也不會對數據庫產生任何影響.

Transient------Session.save()------>Persistent
Object---------Session.load()------>Persistent
(Object------Session(有效期內)-----Persistent)

//由Hibernate返回的Persistent對象
TUser user = (TUser)session.load(TUser.class,new Integer(1));
//Session.load方法中,在返回對象之前,Hibernate就已經將其對象納入
//其實體容器中

Persistent對象<------一一對應------>數據庫中的一條記錄

(3),Detached:
Oberct(Persistent)-----對應的Session實例關閉----->Object(Detached)

TUser user = new TUser();
user.setName("Emma");//user in Transistent
Transaction tx = session.beginTransaction();
session.save(user);//user in Persistent
tx.commit();
session.close();//user in Detached

Detached 與 Transient的區別:
Detached對象可以再次與某個Session實例相關聯而成爲Persistent對象(Transient也可以,但其內部不一樣)---Transient狀態的user與庫表中的數據缺乏對應關係,而Deatached狀態的user卻在庫表中存在相應的記錄(由主鍵唯一確定)。

人工製造一個Detached對象:
TUser user = new TUser();
user.setName("Emma");
//硬編碼爲其指定主鍵值(假設庫表中存在id=1的記錄)
user.setId(new Integer(1));
//此時user成爲一個"人造detached對象"

Transaction tx = session.beginTransaction();
session.update(user);//Session根據其主鍵值,將其變爲Persistent
user.setAge(new Integer(20));
tx.commit();

session.flush();
session.close();

Object(Persistent)---Session.delete()--->Object(Transient)
Transient:從庫表中找不到對應的記錄。
Detached:從庫表中能找到對應的記錄(只是沒有與session進行關聯)。

一般而言,應該避免直接將PO傳遞到系統中的其他層面,一種解決辦法是,通過構造一個新的VO,通過屬性父指示器具備與PO相同的屬性值,並以其爲傳輸媒質(實際上,這個VO被用作Data Transfer Object,即DTO),將此VO傳遞給其他層面以實現必須的數據傳送。
屬性複製:AJC Beanutils組件.例:
TUser user = new TUser();
TUser anotherUser = new TUser();
user.setName("Emma");
user.setAge(new Integer(1));
try{
  BeanUtils.copyProperties(anotherUser,user);
}catch(){
}

2,實體對象識別

(1),實體身份識別(Data Identity)
如何判定兩個實體對象是否相等?
站在數據庫的角度,我們認爲在一個庫表結構中,主鍵可以唯一確定一條記錄,那麼對於擁有同樣主鍵值的實體對象,則認爲他們等同。
在持久層之外,對象是否相等也遵循着特定領域中的邏輯規則。---這樣的邏輯規則如何體現在我們的實體對象之間?--------覆蓋Object.equals()
public boolean equals(Object object){
  TUser user = (TUser)object;
  return this.getId().equals(user.getId());
}
public int hashCode(){
  return this.getId().intValue();
}

問題:
TUser user = (TUser)session.load(TUser.class,new Integer);

TAddress addr1 = new TAddress();
addr1.setAddress("Shanghai");
TAddress addr2 = new TAddress();
addr2.setAddress("Guangdong");

user.getAddresses().add(addr1);//addr1.id=null;
user.getAddresses().add(addr2);//addr2.id=null;

System.out.println("Items in set : " + user.getAddresses().size());

--主鍵值生成機制,id只有在Session.save()方法執行之後纔會被設置。--解決方法:
1,不覆蓋equals/hashCode方法的情況下將面臨:實體對象的跨Session識別。包含了兩個針對同一庫表記錄的實體,當Session.save時,將得到一個NonUniqueObjectException異常。---只是用一個session實例可避免。
2,實現值比對。
Elipse中免費插件:a) Commonclipse b) Commons4E.
注意:只需針對實體類的屬性進行處理,而不要設計實體類所關聯的集合類的比對,否則在多對多關係中很容易引發一些其它的問題。
3,業務關鍵信息判定---值比對的一個子集。

(2),髒數據檢查
---並非廢棄或者無用的數據,而是指一個數據對象所攜帶的信息發生了改變之後的狀態。
Transaction tx = session.beginTransaction();
TUser user = (TUser)session.load(TUser.class,new Integer(1));
//此時user對象處於由數據庫讀出的原始狀態

user.setAge(30);//此時user對象所攜帶的信息發生了變化,成爲所謂的“髒數據”
tx.commit();

事務提交時,Hibernate會對session中的PO進行檢測,判斷哪些發生了變化,並將方生變化的數據更新到數據庫中。

Hibernate如何進行髒數據識別?
(1),數據對象監控---通過攔截器對數據對象的設值方法(setter)進行攔截,一旦setter方法被調用,則將其標誌爲“待更新”狀態。
(2),數據版本對比---在持久層框架中維持數據對象的最近讀取版本,將提交數據與此進行對比。-------Hibernate採用這種策略。
tx.commit();
--public void commit() throws HibernateException{
    ......
    if(session.getFlushMode()!=FlushMode.NEVER){
      session.flush();
    }
    ......
  }
----public void flush() throws HibernateException{
      ......
      flushEverything();//刷新所有數據,首先完成一些預處理工作,之後即調用flushEntities方法對當前Session中的實體對象進行刷新。--判定髒數據
      execute();//執行數據庫SQL完成持久化動作
    }

(3),unsaved-value
---數據(VO)保存(Insert)時(顯式保存 or 根據級聯關係對聯接類進行保存),Hibernate將根據這個值來判斷對象是否需要保存。

3,數據緩存
---持久層性能提升的關鍵。

緩存:是數據庫數據在內存中的臨時容器,它包含了庫表數據在內存中的臨時拷貝,位於數據庫與數據訪問層之間。

ORM數據讀取:首選緩存,查到則返回---避免了數據庫調用的性能開銷。
對於企業級應用,數據庫與應用服務器位於不同的物理服務器,也就是每次數據庫訪問都是一次遠程調用--Socket的創建於銷燬,數據的打拆包,數據庫執行查詢指令,網絡傳輸的延時等。---本地內存中數據緩存的價值。

(1)數據緩存策略
ORM的數據緩存應包含:1,事務級緩存(事務範圍內):基於Session生命週期。 2,應用級/進程級緩存(在SessionFactory層實現),所有由此SessionFactory創建的Session實例共享此緩存。---多實例併發運行會產生問題:A,B共享同一數據庫,各自維持其緩存,A對數據庫進行了更新,B緩存數據仍爲更新前。--> 3,分佈式緩存(在所個應用實例,多個JVM之間共享的緩存模式),由多個應用級緩存實例組成集羣。---解決了多實例併發運行過程中的數據同步問題。
注意:如果當前應用於其它應用共享數據庫,採取一些保守策略(避免緩存機制的使用)可能更加穩妥。

(2),Hibernate數據緩存
(1),內部緩存(Session Level)
(2),二級緩存(SessionFactory Level)

Hibernate緩存發揮作用的情況:
(1),通過id(主鍵)加載數據時
---Session.load(),Session.iterate()
(2),延遲加載

內部緩存:應用事務級緩存,由Hibernate自動維護,可通過以下方法手動干預。
Session.evict---將某個特定對象從內存緩存中清除。
Session.clear---清空內部緩存。

二級緩存:涵蓋了應用級緩存和分佈式緩存。
Session在進行數據查詢操作時,會首先在自身內部的一級緩存中進行查找,如果一級緩存未能命中,則在二級緩存中查詢,如果二級緩存命中,則以此數據作爲結果返回。

引入二級緩存需要考慮的問題:
(1),數據庫是否與其它應用共享
(2),應用是否需要部署在集羣環境中

滿足以下條件,則可將其納入緩存管理
(1),數據不會被第三方應用修改
(2),數據大小載客接受的範圍之內
(3),數據更新頻率較低
(4),同一數據可能會被系統頻繁使用
(5),非關鍵數據(關鍵數據,如金融賬戶數據)

(3),第三方緩存實現
(1),JCS--某些情況下可能導致內存泄漏以及死鎖。
(2),EHCache--默認---無法做到分佈式緩存。
(3),OSCache
(4),JBoss Cache--提供分佈式緩存(Repplication方式)
(5),SwarmCache--提供分佈式緩存(invalidation方式)

Hibernate中啓用二級緩存,需配置hibernate.cfg.xml如下:
<hibernate-configutarion>
<session-factory>
......
<property name="hibernate.cache.provider_class">
  net.sf.ehcache.hibernate.Provider
<property>
......
</session-factory>
<hibernate-configuration>
還需要配置ehcache.xml。
之後,需要在我們的映射文件中指定各個映射實體(以及collection)的緩存同步策略:
<class name="TUser">
  <cache usage="read-write"/>
  ...
  <set name="addresses" ...>
    <cache usage="read-write"/>
    ...
  </set>
</class>

(4),緩存同步策略
--爲了使得緩存調度遵循正確的應用級事務隔離機制,必須爲每個實體類指定相應的緩存同步策略。
4種內置的緩存同步策略:read-only,nonstrict-read-write,read-write,transactional(JTA,此時的緩存類似一個內存數據庫)

5,事務管理(ACID)
(1),數據庫事務管理隔離等級
事務隔離:通過某種機制,在並行的多個事務之間進行分隔,使每個事務在其執行過程中保持獨立(如同當前只有此事務單獨運行)。
Hibernate中的事務隔離依賴於底層數據庫提供的事務隔離機制。
數據操作過程中可能出現的3種不確定情況:
髒讀取:一個事務讀取了另一個並行事務未提交的數據。
不可重複讀取:一個事務再次讀取之前曾讀取過的數據時,發現該數據已經被另一個已提交的事務修改。
虛讀:一個事務重新執行一個查詢,返回一套符合查詢條件的紀錄,但這些記錄中包含了因爲其它最近提交的事務而產生的新紀錄。
4個事務隔離等級:
Read Uncommitted,Read Committed,Repeatable Read,Serializable
(2),Hibernate事務管理
---Hibernate是JDBC的輕量級封裝,本身並不具備事務管理能力。在事務管理層,Hibernate將其委託給底層的JDBC或JTA,以實現事務的管理和調度。
(3),基於JDBC的事務管理---如同JDBC
(4),基於JTA的事務管理---提供了跨Session的事務管理能力。JTA事務管理由JTA容器實現,JTA容器對當前加入事務的衆多Connection進行調度,實現其事務性要求。參與JTA事務的Connection需避免對事務管理進行干涉。也就是說如果採用JTA Transaction,就不應該再調用Hibernate的Transaction功能。

6,鎖
---給我們選定的目標數據上鎖,使其無法被其它程序修改。

(1),悲觀鎖---依靠數據庫層提供的鎖機制。
String hqlStr = "from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE);//加鎖(for update)
List userList = query.list();//執行查詢,獲取數據

query.setLockMode對查詢語句中,特定別名(user)所對應的記錄進行加鎖。

Hibernate的加鎖模式(Hibernate內部使用)有:
LockMode.NONE:無鎖機制
LockMode.WRITE:Hibernate在Insert和Update記錄的時候會自動獲取。
LockMode.READ:Hibernate在讀取記錄的時候會自動獲取。
依賴數據庫的悲觀鎖機制(應用層):
LockMode.UPGRADE:利用數據庫的for update子句加鎖。
LockMode.UPGRADE_NOWAIT:Oracle的特定實現,利用Oracle的for update nowait子句實現加鎖。
注意:應該查詢開始之前設定加鎖。

(2),樂觀鎖
---大多是基於數據版本記錄機制實現。
數據版本:即爲數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過爲數據庫表增加一個“version”字段來實現。
樂觀鎖策略:提交版本必須大於記錄當前版本才能執行更新。

添加一個Version屬性描述符
<hibernate-mapping>
  <class name="TUser" table="T_USER" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">
    <id></id>
    <version column="version" name="version" type="java.lang.Integer"/>
  </class>
</hibernate-mapping>
注意:version節點必須出現在ID節點之後。
違反樂觀鎖策略時:tx.commit()處拋出StaleObjectStateException異常,並指出版本檢查失敗,當前事務正在試圖提交一個過期數據。通過捕捉這個異常,我們就可以在樂觀鎖校驗失敗時進行相應處理。

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