2009年1月24日星期六

Hibernate event/interception mechanism

這幾天在寫time entry interceptor,遇到很大的困難,以前用JPA寫明明很容易。現在回看,最主要的原因不在於兩者機制的不同,而是在於 merge entity 和 修改已經 persisted 的 entity 的分別。

在人仔台工作的時候,系統用的機制是把整個entity作為一個form backup object丟出去讓jsp render,就算沒有用的field也會一併用hidden input的方法放在form中。然後user submit form的時候就拿回一隻原整的deattached entity,可以直接save。基於這個基制,每一次update都會發生一個merge的event。

然而現在Skelaxy則完全不同,User只會submit有用的資料。在update的時候,系統是拿一隻persisted的entity出來,再把web request的VO抄過去persisted entity,然後update。這個時候的update不但沒能觸發merge event,甚至是甚麼也不會做*。要真正update一隻persisted entity 須要call flush,但就算是flush也不會發生merge event。

Hibernate的LifeCycle, Interceptor 和 Event

Interceptor
我最初implement的時候是使用interceptor,這是我見過最白痴的event 機制,是我這幾天惡夢的開始。首然每一個session factory只能有一個interceptor就有夠麻煩,要另外寫一隻chainedInterceptor去自己實現multiple interceptor。然後發覺onSave和onFlushDirty修改那隻entity object根本沒有用,原來是要知道field的名稱,然後在那個field名的array裏尋找position,再用這個position修改另一個array裏相同position的value。知道這之後我實在無言以對,戇鳩。

然而事實並不原全是這樣。首先修改那個object在沒有用version的情況下是可行的,只要return的時候告訴hibernate你沒有修改到任何東西,那它就不會把state array裏的value放回object裏,可是我們偏偏用了version,無論你return true or false它也會把state array抄回去,因為它會修改state裏version的value。

所以interceptor被排除了。


LifeCycle
接著發現一個叫LifeCycle的東西,首先這個東西侵入性很強,迫你implement一個Life Cycle。由於我要做的是time entry的interceptor,所以變相要求每一隻entity object也會extends一個implement了LifeCycle的base object。也算了,當時我不知道還有listener的存在。
LifeCycle中有一系列的onXXX,我須要的很明顯是onSave和onUpdate。onSave是很成功了,可是onUpdate卻怎樣也trigger不了,實在想哭。後來看到原來要直接call update()才會trigger,我晃然大悟原來是saveOrUpdate()不能呀,但實驗過後,我的心情再次掉進另一深淵,最後也悻悻然放棄。
原因就如開頭所述,update指的是merge,因為我update的是一隻persisted的entity,當然不會有任何事情發生。可是誰會想到onUpdate指的是merge呢……

Listener
再後來發現hibernate還有一個listener的系統,hibernate還真是麻鳩煩,居然有3個event system。然而最麻煩的是根本不知道怎樣用,hibernate的 documentation幾近完全沒有明,這叫誰用呀﹖我要一直爆source爆到去EventListeners才大概明白要怎麼做,可是它的event數量太過驚人,完全不知所措。

我最初選用的是pre-insert和pre-update,但卻發現它根本什麼也不做!全部都說violaten了creationDate和modificationDate not null constraint。然後我轉用onSave, onSaveOrUpdate, onUpdate的event。這幾個比較特別,因為它們是共用SaveOrUpdateEvnetListener的(再次,沒有看過source,你是不可能知道的)。還有就是有一個DefaultSaveOrUpdateEventListener的東西,它很重要,沒有它的話save, update, saveOrUpdate會完全衰失功能,可是你放了自己的listener進去之後,default listener就沒有了,必須把自己的listener和default listener同時放進去,還要留意先後次序。還有就是,你不能知道何時是update,何時是insert,當然你可以從default listener裏抄code過來,或者是extend default listener然後override一些method,但一升級hibernate就痛苦了。要留意這麼多事情,可想而知,我經歷了多少失敗。
但最後它真的成功做到我想做的事了!

不過至此並未完結,因為後來發覺很多沒有必要update的entity也隨著flush一起update了。原來在call update()時把modificationDate更改了導至沒有改變過的entity被當作修改了;這只是其次,原來flush的時候,它會把要cascade的collection都會探訪一次saveOrUpdateEventListener,這可嚴重了,因為這個collection之下可能還有cascade,而且collection size是不能估計的……

難道到此就完蛋了嗎﹖
非也,在再次爆source,有了新的希望。pre-insert之所以失敗,是因為constraint checking是在pre-insert之前發生,即是說其實pre-update應該是可行的(之前因為pre-insert的失敗,也沒有測試pre-update),因為pre-update時creationDate和modificationDate已經不是null了。可是pre-insert是徹底的沒救了,總不能為了目的而不擇手段地把not null constraint移去吧,再說這牽涉到大量xml修改(如果是annotation還好些)。再想一想onSaveOrUpdate只要update出問題,不正好互補嗎?至此,功得終於完滿。

總結
這次的經歷令我幾乎完全了解了hibernate的運作,算是因禍得福吧。
但同時也說明了它documentation的差劣和event機制的差勁(需要多點站在傳統database的運作設想,沒有人想要hibernate系統為本的event)

註*: 也不是完全沒有動作,它會做cascade

沒有留言: