本文將簡單談談我對 EJB 3.0 的兩種 Persistence Context 和 Seam-managed Persistence Context 的不同點的理解、所要解決的問題和我自己所疑惑的問題。
EJB 3.0 (JPA) 的 Persistence Context
大家在使用 EJB 3.0 的時候會注意到 EJB 3.0 中的容器管理 Persistence Context 有兩種類型,一種是 Transaction,另一種是 Extended。這是一個較 Hibernate 的 Session 所沒有的概念,Session 沒有兩種不同的類型,而且最重要的是 Session 不是容器管理的,這裡的容器指的是 App server 容器。這裡暫時不談論 Persistence Context 與 Session 之間的異同,主要談談兩種 Persistence Context 之間的不同。學過 ORM 的同學都知道,當 Persistence Context 是打開狀態的時候,Model 就處於被管理的狀態中;當 Persistence Context 關閉之後,Model 就處於了 Detached 狀態。
上面這些特性對於 Transaction 或 Extended 的 Persistence Context 都是一樣的,不同的地方在於 Persistence Context 何時被打開關閉。由於絕大多數情況下 Persistence Context 是被容器管理的(如果你不嫌累也可以自己控制 Persistence Context),所以在 EJB 3.0 應用中看不到打開或關閉 Persistence Context 的代碼(Spring + Hibernate 的應用也同樣如此,Hibernate Session 的管理工作可以交給 Spring 來做)。
其實,Transaction 和 Extended Persistence Context 的不同之處也就在於容器何時打開或關閉 Persistence Context。Transaction 類型的 Persistence Context 的打開和關閉是和事務的打開和關閉是同步的。也就是說在一個事務開始之後,Persistence Context 才會開始;在事務關閉的時候,相應的 Persistence Context 也會被關閉。
Extended 類型的 Persistence Context 的打開和關閉是和 Stateful Session Bean 的生命周期同步的,是跨越事務的。也就是說,從 SFSB 的初始化開始,直到銷毀,Persistence Context 都是存在的。你可以在事務之外執行寫操作,但是這是並不會執行真正的數據庫操作,寫操作只是放入了隊列,直到下一個事務,寫操作才會真正地被執行。兩者的不同簡單說來就是 Extended Persistence Context 存在的時間更長。那為什麼要有兩種不同的 Persistence Context 呢?
當一個 Web 請求到來時,服務器會打開一個線程,這個線程可能會調用一個事務方法,這是一個事務便開始了,當這個請求結束時,線程關閉,事務也隨之結束。由於 Transaction 類型的 Persistence Context 的生存周期是在事務范圍之內的,所以一個 Web 請求的結束也意味著相應的 Persistence Context 的關閉。由於多數 Web 應用在一次 Web 請求內即可完成一個獨立的操作,所以大部分情況下 Transaction 的 Persistence Context 是適用的。但是對於一些復雜的應用,一次操作需要跨越多次請求。這種情況下,如果依舊使用 Transcation 的 Persistence Context,由於每次請求結束後,相應的 Persistence Context 都被關閉,相應的 Model 也就變為 Detached 狀態。如果接下來的請求仍然需要這些已經變為 Detached 狀態的 Model 就需要重新 load,使用 merge() 方法來持久化。稍有不適就會產生 LazyInitializationException 和 NonUniqueObjectException。同時,這也提高了操作的復雜程度。
如果使用 Extended Persistence Context 就能解決這些問題。由於 Extended Persistence Context 的生命周期是與 SFSB 的生命周期同步的,所以只要多次請求調用的都是同一個 SFSB 中的方法,有多少次的請求,Persistence Context 總是同一個,其中的 Model 也始終是被管理的。很好地解決了 Persistence Context 在線程之間傳遞的問題,也不會有 LazyInitializationException 和 NonUniqueObjectException 問題的發生。
Seam-managed Persistence Context
EJB 3.0 容器管理之下的 Persistence Context 很不錯,能解決很多問題,但是還是有些問題無法解決。Seam 很強大,如果有些問題 EJB 容器解決不了了,沒關系,把 Persistence Context 交由 Seam 來管理就 OK 了。那 Seam 都能解決哪些 EJB 不能解決的問題呢?先考慮下面兩個問題:
Extended Persistence Context 雖然可以跨越多個事務,但是每個事務照舊調教不誤,這對於想在想讓整個操作作為一次事務的話,該如何去做
如果一個業務的一組請求只是調用同一個 SFSB 的話,那麼 EJB 的 Extended Persistence Context 可以在線程之間傳遞,使 SFSB 的整個生命周期都使用同一個 Persistence Context。但如果業務需要調用不同的 SFSB 的話,如何在 SFSB 之間傳遞。
對於第一個問題,由於 Seam 的 JPA 實現提供者是 Hibernate,而且 Hibernate 提供了一個擴展的 FlushModeType - "Manual"。通過使用這個 FlushModeType,我們可以手工控制何時執行 flush() 操作。在 Seam 的文檔中有關於這部分的介紹 《Seam-managed persistence contexts and atomic conversations 》。文檔中使用了一段簡單的代碼展示 Seam 如何實現所謂的 "atomic conversations"(關於代碼的內容我就不介紹了,大家通過我提供的鏈接來浏覽 Seam 的文檔)。通過這種方式,事務貌似是跨越了整個事務,但我認為 SFSB 中除了調用 flush() 的方法以外的其它方法不是事務的。其實也沒有必要,因為這些方法並沒有執行數據庫操作,所以沒有必要使用事務。當然,如果是樂觀事物的話,使用了對性能影響也不大(這只是我的一點淺薄的理解,歡迎指出錯誤)。只有最後的調用了 flush() 的方法有事務的必要。
這就引發了一個令我不解的問題。請先看這篇文章《Extended Persistence Context in Stateful Session Beans 》。在這篇文章介紹了如何只使用 EJB 3.0 去解決“問題1”。文中的 SFSB 的默認事務屬性是 "NOT_SUPPORTED",也就是說這個 SFSB 中的方法默認不是事務的。只有最後的調用 flush() 的方法使用了 "REQUIRED" 的事務屬性去覆蓋默認設置。也就是最後的方法是事務的,其它的不是。這和 Seam 所做的有區別嗎?感覺沒有區別。但我認為像 Gavin King 這樣的大牛絕不會做無用功的,那問題就是 Seam 實現 "atomic conversations" 的內部細節到底是什麼呢?歡迎大家回答這個問題。
對於第二個問題,可以通過使用 Seam-Managed Persistence Context 來解決。Seam-manged Persistence Context 需要在 components.xml 文件中進行配置,並使用 @In 注入到 Seam 的組件中。由於 Seam 是一個比較新的框架技術,所以關於 Seam 是如何使 Persistence Context 在組件中傳遞並沒有詳細的介紹。應該只是聲明,然後透明地使用即可。在一個 jBPM 流程中被使用到的 Seam 組件,其中的 Persistence Context 應該是可以很容易地被傳遞。(本不應該使用不確定的和模糊的詞語,但無奈現在關於 Seam 的文章資料還是有限,所以我也沒有找到關於 Persistence Context 在組件之間調用的例子)
結束語:這篇文章解釋的問題不多,不過都是我自己的理解。可能有錯誤的地方,歡迎大家指出。同時,又有一些新問題產生了,應該可以通過閱讀 Seam 提供的例子和源代碼得到解釋。但 Seam 的例子只看過三個,更多的還沒有看。看過的 Seam 的源代碼更可以忽略不計。繼續努力吧!!