J2EE持久層持久化上下文的傳播以及會話(Conversation)實現

xmuzyu發表於2008-12-18

目前持久層框架都有一個持久化上下文的概念,下面以比較流行的hibernate以及JPA來做一總結。

如果我們採用OO的方式開發系統,那麼勢必為了減低耦合,增加內聚,我們會通過細粒度的類來實現業務功能,那麼這樣就產生了一個問題,如何將持久化上下文在不同的類(這裡面其實就是Dao類或者DDD裡面的repository)中傳播,比如傳統的開發方式中,一個service裡通過不同的Dao來訪問資料庫,那麼怎麼保證不同的Dao類中用的session以及以及與session對應的持久化上下文是同一個呢?這就涉及到了持久化上下文如何傳播的問題。下面以我熟悉的hibernate以及JPA來來說明一下,在hibernate中,根據採用的底層事務的不同,需要採用不同的策略來實現:

1 Hibernate中持久化上下文的傳播

1.1 採用jdbc事務

此時hibernate通過Threadlocal將當前的session與當前的執行緒繫結在一起,這樣以來只要是同一個執行緒中的呼叫,那麼獲得的session都是同一個,具體來說就是配置hibernate.current_session_context_class屬性為thread,這樣以來hibernate內部就會通過CurrentSessionContext介面的實現類ThreadlocalSessionContext通過threadlocal來將session和當前執行緒繫結在一起。當呼叫SessionFactory的getCurrentSession()方法,返回的就是與當前執行緒繫結的session,從而解決了持久層上下文傳播的問題。

1.2 採用JTA事務

此時hibernate內部是將當前的session以及對應的持久化上下文繫結到了全域性的JTA事務上,這樣以來我們通過sessionFactory的getCurrentSession()方法獲得的就是與當前的JTA事務繫結的session.具體一點就是配置屬性hibernate.current_session_context_class為jta,這樣以來hibernate內部就是通過CurrentSessionContext介面的實現類JTASessionContext來將session與當前的JTA全域性事務繫結在一起,因此當我們通過sf.getCurrentSessionContext()來獲取session時,獲得的就是與當前的JTA繫結到一起的session.

但是在此種情況下,需要特別注意一個問題:不能同時使用hibernate的Transaction介面與getCurrentSession(),因為當前的session是繫結到全域性JTA事務中的,如果通過session.beginTransaction()來開始事務,這說明以前沒有事務,既然沒有事務存在,我們的session又是怎麼繫結到全域性JTA事務上的呢?所以一定要注意:當使用JTA事務,並且用了getCurrentSession()的方法時,一定不要用hiernate的native transaction 介面。但是我們如果我們用openSession(),那麼就可以通過通過hibernate的native Transaction介面來控制JTA事務。

最後還需要弄清楚一點,Hibernate中還有一種繫結持久化上下文的方法,那就是通過設定hibernate.current_session_context_class屬性為managed,hiberante內部就是通過CurrentSessionContext介面的實現類ManagedSessionContext來繫結的。在此種情況下主要是為了實現會話,會話在hibernate中的實現介紹。

2 JPA中持久化上下文的傳播

JPA中持久化上下文的傳播根據採用不同的事務模型而不同,下面分別來說明:

2.1 採用resource-local事務模型

如果採用resource-local事務模型,此種情況也就是在非J2EE應用伺服器的支援下使用。那麼我們的持久化上下文的生命週期是與當前的EntityManager繫結到一起的,所以我們可以在不同的類中傳播相同的EntityManager例項來達到傳播事務上下文的傳播。

2.2 採用JTA事務模型

在此種模型下面,我們有EJB容器的支援,持久化上下文的傳播是藉助於事務上下文來傳播的,在說明如何傳播前首先要明確EJB元件中兩種不同的事務上下文的生命週期:

對於stateless session bean,持久化上下文的生命週期是與當前的系統事務一致的,這也就是無狀態會話bean的事務型的持久化上下文,每當事務結束,持久化上下文也就結束了,所有持久化物件也就變為了脫管的(detached).

對於statefull session bean,持久化上下文的生命週期是與當前的有狀態會話bean一致的,只有當有狀態的會話bean從系統中移除的時候,持久化上下文才關閉,這也就是有狀態會話bean的擴充套件的事務上下文。

搞清楚了這兩種不同的事務上下文的生命週期以後,我們來說一下持久化上下文如何傳播的問題。持久化上下文是通過當前系統事務來傳播的,當一個EJB元件呼叫另一個EJB元件的時候,如果兩個EJB元件的事務範圍是一樣的,那麼持久化上下文就會傳播,下面分幾種情況來說明:

無狀態會話bean之間呼叫:此時如果兩個無狀態會話bean在同一個事務中呼叫,那麼持久化上下文就是同一個,通過當前事務來傳播。

有狀態會話bean呼叫無狀態會話bean:此種情況下如果兩者在同一個事務中,那麼有狀態會話bean的擴充套件的事務上下文會傳播到無狀態會話bean裡,其實還是通過事務來傳播。但是如果被呼叫的無狀態會話bean不支援事務的話(事務屬性設定為not support 或者never),那麼此時持久化上下文不能傳播(JPA規範規定擴充套件的持久化上下文是不能傳播到無事務的stateless session bean)。

有狀態會話bean之間呼叫:此時無論兩個有狀態會話bean是否支援事務,那麼擴充套件的持久化上下文都會傳播,此時就不是通過系統事務來傳播的,而是通過statefull session bean的例項來傳播(但是此時一定要注意有狀態會話bean必須是通過容器注入的或者顯示通過JNDI查詢)。

無狀態的會話bean呼叫有狀態會話bean:一個有事務範圍的持久化上下文的stateless session bean呼叫一個statefull session bean會引發一個錯誤,因為當前的事務上下文不能傳播)

綜上所述,EJB之間的持久化上下文傳播是通過我們的系統事務來傳播的,如果EJB不支援事務(事務屬性設定為not support或者never)),那麼持久化上下文就不會傳播,但是對於擴充套件的持久化上下文,是通過statefull session bean來傳播的,即使沒有事務也可以傳播。

前面說的是關於持久化如何在持久層的不同的類之間傳播的問題,其實無外乎就是通過當前的執行緒和當前的系統事務來傳播,不過對與statefull session bean的擴充套件的持久化上下文,傳播是通過例項來傳播的,在下面的實現會話的討論中,我還會說到這個問題。當我們掌握了原理的時候,遇到一些問題的時候,我們就會很快找到解決方案。

[該貼被admin於2008-12-18 09:47修改過]

相關文章