1. 程式人生 > >博觀而約取,厚積而薄發

博觀而約取,厚積而薄發

這段時間本人利用空閒時間解讀了一下Hibernate3的原始碼,饒有收穫,願與大家共享。

廢話不多說,首先我們先對Hibernate有一個大致的印象

l  設計模式Hibernate=監聽器,實際上是回撥

l  Hibernate3支援攔截器

Hibernate配置方面的大原則:

l  bhn.xml檔案所有配置都是描述本實體,除了cascade描述級聯,即如何將本實體的操作(增刪查改)傳遞給關聯方。

l  inverse屬性表示本實體是否擁有主動權,在一條cascade鏈路傳遞過程中,當出現inverse=false表示不再返回原cascade鏈路,而是從此處重新開始鏈路。inverse只有在非many方才有,也就是many-to-many或者one-to-many的set,List等。
下面是註明inverse=true與inverse=false的cascade鏈路的區別:

說明:若關聯屬性inverse=true,操作的結果將是校對A的屬性所生成的sql;若關聯屬性inverse=false,結果將是丟棄先前A的操作,而轉向對B的屬性的校驗所生成的sql;如果B中的屬性也關聯著inverse=false,則仍丟棄B繼續新開啟鏈路,直至沒有關聯方為inervse=false。不必擔心,關聯著的雙方只有一方擁有inverse屬性,所以不會一直傳遞下去。還有,丟棄了先前的操作不等於之前的物件操作無效,其效果相當於,原先的session.save(A),變成了session(A.B)而在B校對屬性時總會找回A物件的。

測試用例:(暫不考慮inverse=false)

測試1:save()一個實體物件操作,預計insert發生在擁有外來鍵方的表,擁有外來鍵方的表是一對多中的多方。

結論1:如果實體物件的外來鍵屬性為null,表示不會產生關聯,可直接生成sql;如果外來鍵屬性不為空,根據配置中的cascade去做關聯。如果cascade=all則生成此表的insert和關聯表update的sql,也就是說此時要求關聯屬性的主鍵id不能為null;如果cascade=save-update則生成此表的insert和關聯表的insert/update的sql(關鍵屬性的主鍵為null為insert,否則為update)。

見下圖:

 

舉例說明:

---------PO類:A中有B型別的關聯屬性

class A{

    private int id;

    private B b;

}

class B{

   private int id;

   private String str;

}

---------呼叫處關鍵程式碼:

   A a=new A();//待操作的實體物件

   B b=new B();//關聯屬性

   a.setB(b);//設定關聯屬性

   //----

   //a.setId(1);//save()操作不允許預定一個id,hibernate的Id必須使用配置中的方式生成

   //a.setB(null);//關聯屬性為null

   //----

   b.setId(1);/*關聯屬性的主鍵有值。只有cascade鏈在B物件校驗為update操作才有效,也就是說A.bhn.xml中B

                    *的級聯設為cascade=save-update。*/

   b.setId(null);/*關聯屬性的主鍵為null。支援cascade=all與cascade=save-update的操作,最終在B生成的

                       * 是insert操作*/

   //--------

   b.setStr("當前用於測試");//------資料項

   //-------操作

   session.save(a);

原始碼解讀:(粗略)

Hibernate主要是事件監聽模式(回撥的一種實現),其核心類為Session類,Session類承載了CRUS操作和Commit操作。

補充知識點:回撥的好處在於事件源物件eventSource和資料物件Object,被集中在監聽器Listener裡完成業務,集中的好處在於新寫Listener就可以達到功能的擴充套件。Listener的處理方法的引數為,事件物件eventObject,事件物件包含事件源eventSource和資料物件,相當於Listener傳的是兩個引數,也就是說Listener得到了此資料模型中的所有資料,自然可以完成任何功能,其餘部分在模型中可理解為僅是為了給Listener傳參做準備。通常的執行流程是事件源先被呼叫方法,所以事件源的方法裡完成了業務功能,所謂回撥就是形式上還是呼叫了事件源的方法,但是業務功能的程式碼卻在第三方的Listener類中完成,而事件源的方法裡只是為了實現如何傳參給Listener。這樣就像是與傳統程式設計相反由Listener去呼叫事件源。

案例1:Session的S查詢操作

過程略:

小結:Hibernate的Select操作直接生成sql,當然通過了記憶體快取才生成的sql。

案例2:Session呼叫CRU操作(非S操作,增刪改)。

以Save()操作為例

步驟1. SessionImpl.save(obj); SessionImpl.save(null,obj);--從save(Object,Object)統一呼叫

步驟2. new SaveOrUpdateEvent(entityName, object, this)—建立並組裝事件物件(用於Listener的引數)

步驟3. SessionImpl.fireSave(SaveOrUpdateEvent);--觸發事件,即呼叫Listener的處理方法,目的在於傳參步驟2中new出的SaveOrUpdateEvent事件物件。

程式碼如下:

private Serializable fireSave(SaveOrUpdateEvent event) {

errorIfClosed();

checkTransactionSynchStatus();

SaveOrUpdateEventListener[] saveEventListener =listeners.getSaveEventListeners();

for ( int i = 0; i < saveEventListener.length; i++ ) {

saveEventListener[i].onSaveOrUpdate(event);

}

return event.getResultId();

}
紅色為關鍵程式碼,其中listenersSessionEventListeners屬性。

EventListeners包含有一系列的監聽器,而各種監聽器以陣列的形式允許有多個並且按順序呼叫。本例中呼叫的監聽器種類為saveOrUpdateEventListeners,處理方法為onSaveOrUpdate()方法。實現onSaveOrUpdate(event)的類是DefaultSaveOrUpdateEventListener。所以業務實現程式碼應該在DefaultSaveOrUpdateEventListener.onSaveOrUpdate()中

EventListeners類中的一系列Listener []屬性:

    private LoadEventListener[] loadEventListeners = { new DefaultLoadEventListener() };

private SaveOrUpdateEventListener[] saveOrUpdateEventListeners = { newDefaultSaveOrUpdateEventListener() };

private MergeEventListener[] mergeEventListeners = { newDefaultMergeEventListener() };

private PersistEventListener[] persistEventListeners = { newDefaultPersistEventListener() };

private PersistEventListener[] persistOnFlushEventListeners = { newDefaultPersistOnFlushEventListener() };

private ReplicateEventListener[] replicateEventListeners = { newDefaultReplicateEventListener() };

private DeleteEventListener[] deleteEventListeners = { newDefaultDeleteEventListener() };

private AutoFlushEventListener[] autoFlushEventListeners = { newDefaultAutoFlushEventListener() };

private DirtyCheckEventListener[] dirtyCheckEventListeners = { newDefaultDirtyCheckEventListener() };

private FlushEventListener[] flushEventListeners = { newDefaultFlushEventListener() };

private EvictEventListener[] evictEventListeners = { newDefaultEvictEventListener() };

private LockEventListener[] lockEventListeners = { new DefaultLockEventListener() };

private RefreshEventListener[] refreshEventListeners = { newDefaultRefreshEventListener() };

private FlushEntityEventListener[] flushEntityEventListeners = { newDefaultFlushEntityEventListener() };

private InitializeCollectionEventListener[] initializeCollectionEventListeners =

new DefaultInitializeCollectionEventListener() };

步驟4. DefaultSaveOrUpdateEventListener.onSaveOrUpdate()----業務功能程式碼。由於方法的實現涉及內容比較多,此處暫不作詳細介紹。大致功能有為了補充齊全SaveOrUpdateEvent事件物件的其他屬性,可見事件物件是記錄Hibernate操作過程的容器。

步驟5.根據我們提交給Hibernate的Session的CRU指令操作,重複步驟1到步驟4多次直到最後到tran.commit()操作,tran是Session開啟的Transction物件,預設JDBCTransction實現,根據hibernate.cfg.xml中配置確定了在new Configuration().configure()創立的,假定JDBCTransction.commit()的實現,具體程式碼如下:

publicvoid commit() throws HibernateException {

if (!begun) {

thrownew TransactionException("Transaction not successfully started");

}

log.debug("commit");

if ( !transactionContext.isFlushModeNever() && callback ) {

transactionContext.managedFlush();//if an exception occurs during flush, user must call rollback()

}

notifyLocalSynchsBeforeTransactionCompletion();

if ( callback ) {

jdbcContext.beforeTransactionCompletion( this );

}

try {

commitAndResetAutoCommit();

log.debug("committed JDBC Connection");

committed = true;

if ( callback ) {

jdbcContext.afterTransactionCompletion( truethis );

}

       notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_COMMITTED );

}

catch (SQLException e) {

log.error("JDBC commit failed", e);

commitFailed = true;

if ( callback ) {

jdbcContext.afterTransactionCompletion( falsethis );

}

notifyLocalSynchsAfterTransactionCompletion( Status.STATUS_UNKNOWN );

thrownew TransactionException("JDBC commit failed", e);

}

finally {

closeIfRequired();

}

}

其中紅色部分是生成sql的方法,這裡暫不展開說明,生成sql依據的是上面步驟1-4所補充完整的事件物件。

綠色部分是Hibernate3對攔截器的支援,我們都知道Hibernate3比較之前的版本的一個重要的新特性就是支援攔截器,而這一特性就體現在此處。

小結:Session的非查詢操作,只有到tran.commit()才生成sql,期間所有的CRU操作的結果都存放到對應的EventObject物件,對於儲存C操作和更改U操作都存放在SaveOrUpdateEvent,刪除操作R存放在DeleteEvent,而後commit()完成所有EventObject生成sql的規則。

各種操作與相應的流程如下面:

操作/流程

入口

建立事件物件

觸發事件

事件處理方法

save

SessionImpl.save()

new SaveOrUpdateEvent()

fireSave()

DefaultSaveOrUpdateEventListener

update

SessionImpl.update()

new SaveOrUpdateEvent()

fireUpdate()

DefaultSaveOrUpdateEventListener

Delete

SessionImpl.delete()

new DeleteEvent()

fireDelete()

DefaultDeleteEventListener

補充:所有業務處理監聽器都在org.hibernate.event.def包下

總結:

1.save操作:commit()時,資料庫執行,並增加快取中的物件。

2.delete操作:要求含有主鍵,commit()時,資料庫執行,並刪除快取中的物件。

       如果刪除執行記錄數無影響,即沒有找到要刪除的記錄,報錯。

3.Select操作:直接查詢資料庫,更新快取中的物件。

4.update操作:要求含有主鍵,commit()時,資料庫執行,並更新快取中的物件。

       如果更新執行記錄數無影響,即沒有找到要修改的記錄,報錯。

使用Hibernate時需要確定表的結構,只有確定了表的結構才能確定表的執行順序,雖然Hibernate的目地是讓我們程式設計只關心要操作物件,但是我們要明白維護(cascade鏈路方向)的方向是單向的,即使我們說Hibernate支援雙向關聯。

  • 維護方向是cascade和inverse配置出來的,Hibernate會遵循配置,去生成sql;
  • 另外所謂的雙向關聯只不過是維護方向單向查詢出所關聯的物件而後在記憶體中進行回填。如,User:Address=1:1,雙向關聯的目標是查詢User物件,可得到User=User.getAddress().getUser()的結果,Hibernate的實現是單向查詢得到User即關聯屬性Address物件,而後User.getAddress().setUser(User)進行回填。

SessionImpl==EventSource 事件源

SaveOrUpdateEvent==Event事件物件