cmd

          在Spring中集成Hibernate事務(wù)

          本文主要探討怎么用Spring來裝配組件及其事務(wù)管理。在J2EE工程里連接到一個簡單的數(shù)據(jù)庫并不是什么難題,但是如果要綜合組裝企業(yè)類的組件就變得復(fù)雜了。一個簡單的組件有一個或多個數(shù)據(jù)庫支撐,所以,我們說到整合兩個或多個的組件時,我們希望能夠維持跨組件的許多數(shù)據(jù)庫的運(yùn)作的原子性。

            J2EE提供了這些組件的容器,可以保證處理的原子性和獨(dú)立性。在沒有J2EE的情況下我們可以用Spring。 Spring基于IoC模式(即反轉(zhuǎn)模式),不僅可以配置組件服務(wù),還可以配置相應(yīng)的方法。為了更好的實現(xiàn)本文的目的,我們使用Hibernate來做相應(yīng)的后臺開發(fā)。

            裝配組件事務(wù)

            假設(shè)在組件庫里,我們已經(jīng)有一個審核組件(audit component),里面有可以被客戶端調(diào)用的方法。接著,當(dāng)我們想要構(gòu)建一個處理訂單的體系,我們發(fā)現(xiàn)設(shè)計需要的OrderListManager組件服務(wù)同樣需要審核組件服務(wù)。OrderListManager創(chuàng)建和管理訂單,每一個服務(wù)都含有自己的事務(wù)屬性。當(dāng)這時調(diào)用審核組件,就可以把 OrderListManager的處理內(nèi)容傳給它。也許將來新的業(yè)務(wù)服務(wù)(business service)同樣需要審核組件,那這時它調(diào)用的事務(wù)內(nèi)容已經(jīng)不一樣了。在網(wǎng)絡(luò)上的結(jié)果就是,雖然審核的功能保持不變,但是可以和別的事件功能組合在一起,用這些方法屬性來提供不同的運(yùn)行時的處理參數(shù)。

            在圖1中有兩個分開的調(diào)用流程。在流程1里,如果客戶端含有一個TX內(nèi)容,OrderListManager 要由一個新的TX開始或者參與其中,取決于客戶端在不在TX里以及OrderListManager方法指定的TX屬性。這在它調(diào)用 AuditManager方法的時候仍然適用。

            圖1. 裝配組件事務(wù)

            EJB體系通過裝配者聲明正確的事務(wù)屬性來獲得這種適應(yīng)性。我們不是在探討是否聲明事務(wù)管理,因為這會使運(yùn)行時的事務(wù)參數(shù)代碼發(fā)生改變。幾乎所有的J2EE工程提供了分布的事務(wù)管理來配合提交協(xié)議例如X/Open XA specification。

            現(xiàn)在的問題是我們能不能不用EJB來獲得相同的功能?Spring是其中一種解決方案。來看一下Spring如何處理這樣的問題:

            用Spring來管理事務(wù)

            我們將看到的是一個輕量級的事務(wù)機(jī)制,實際上,它可以管理組件層的事務(wù)集成。Spring就是如此。它的優(yōu)點(diǎn)是我們可以不用捆綁在J2EE的服務(wù)例如JNDI數(shù)據(jù)庫。最棒的是如果我們想把這個事務(wù)機(jī)制與已經(jīng)存在的J2EE框架組合在一起,沒有任何問題,就好像我們找到了杠桿中完美的支撐點(diǎn)一樣。

            Spring的另一個機(jī)制是使用了AOP框架。這個框架使用了一個可以使用AOP的Spring bean factory。在Spring特定的配置文件applicationContext.xml里通過特定的組件層的事件來指定。

          <beans>

          <!-- other code goes here... -->

          <bean id="orderListManager"
                  class="org.springframework.transaction
                  .interceptor.TransactionProxyFactoryBean">
          <property name="transactionManager">
                  <ref local="transactionManager1"/>
          </property>
          <property name="target">
                  <ref local="orderListManagerTarget"/>
          </property>
          <property name="transactionAttributes">
                  <props>
                          <prop key="getAllOrderList">
                                  PROPAGATION_REQUIRED
                          </prop>
                          <prop key="getOrderList">
                                  PROPAGATION_REQUIRED
                          </prop>
                          <prop key="createOrderList">
                                  PROPAGATION_REQUIRED
                          </prop>
                          <prop key="addLineItem">
                             PROPAGATION_REQUIRED,
                             -com.example.exception.FacadeException
                          </prop>
                          <prop key="getAllLineItems">
                                  PROPAGATION_REQUIRED,readOnly
                          </prop>
                          <prop key="queryNumberOfLineItems">
                                  PROPAGATION_REQUIRED,readOnly
                          </prop>
                  </props>
          </property>
          </bean>

          </beans>

            一旦我們在服務(wù)層指定了事務(wù)屬性,它們就被一個繼承org.springframework.transaction.PlatformTransactionManager 接口的類截獲. 這個接口如下:

          public interface PlatformTransactionManager{
                  TransactionStatus getTransaction
                          (TransactionDefinition definition);
                  void commit(TransactionStatus status);
                  void rollback(TransactionStatus status);
          }

            Hibernate事務(wù)管理

            一旦我們決定了使用Hibernate作為ORM工具,我們下一步要做的就是用Hibernate特定的事務(wù)管理實例來配置。

          <beans>

          <!-- other code goes here... -->

          <bean id="transactionManager1"
                  class="org.springframework.orm.hibernate.
                          HibernateTransactionManager">
                  <property name="sessionFactory">
                          <ref local="sessionFactory1"/>
                  </property>
          </bean>

          </beans>

            我們來看看什么是“裝配組件事務(wù)”,你也許注意到了那個OrderListManager 特有的TX屬性,那個服務(wù)層的組件。我們的工程的主要的東西在表2的BDOM里:

            圖 2. 業(yè)務(wù)領(lǐng)域?qū)ο竽P?(BDOM)

            為了用實例說明,我們來列出工程里的非功能需求(NFR):

            ---事務(wù)在數(shù)據(jù)庫appfuse1里保存。
            ---審核時要登入到另一個數(shù)據(jù)庫appfuse2里,出于安全的考慮,數(shù)據(jù)庫有防火墻保護(hù)。
            ---事務(wù)組件可以重用。
            ---所有訪問事件必須經(jīng)過在事務(wù)服務(wù)層的審核。

            出于以上的考慮,我們決定了OrderListManager 服務(wù)將委托任何審核記錄來調(diào)用已有的AuditManager 組件.這產(chǎn)生了表3這樣更細(xì)致的結(jié)構(gòu):

            圖 3. 組件服務(wù)結(jié)構(gòu)設(shè)計

            值得注意的是,由于我們的NFR,我們要映射OrderListManager相關(guān)的事物到appfuse1 數(shù)據(jù)庫里去,而審核相關(guān)的到appfuse2。這樣,任何審核的時候 OrderListManager 組件都會調(diào)用AuditManager 組件。我們認(rèn)為OrderListManager 組件里的所有方法都要執(zhí)行, 因為我們通過服務(wù)來創(chuàng)建次序和具體項目。那么AuditManager 組件里的服務(wù)呢? 因為它做的是審核的動作,我們關(guān)心的是為系統(tǒng)里所有的事務(wù)記錄審核情況。這樣的需求是,“即使事務(wù)事件失敗了,我們也要記錄登錄的審核情況”。 AuditManager 組件同樣要有自己的事件,因為它同樣與自己的數(shù)據(jù)庫有關(guān)聯(lián)。如下所示:

          <beans>

          <!—其他代碼在這里-->
          <bean id="auditManager"
                  class="org.springframework.transaction.
                   interceptor.TransactionProxyFactoryBean">
                  <property name="transactionManager">
                          <ref local="transactionManager2"/>
                  </property>
                  <property name="target">
                          <ref local="auditManagerTarget"/>
                  </property>
                  <property name="transactionAttributes">
                          <props>
                                  <prop key="log">
                                          PROPAGATION_REQUIRES_NEW
                                  </prop>
                          </props>
                  </property>
          </bean>

          </beans>

            我們現(xiàn)在把注意力放到這兩個事務(wù)createOrderList 和 addLineItem中來,作為我們的試驗。同時注意我們并沒有要求最好的設(shè)計——你可能注意到了 addLineItem 方法拋出了 FacadeException, 而 createOrderList 沒有。在產(chǎn)品設(shè)計中,你也許希望每一個方法都處理異常。

          public class OrderListManagerImpl
                  implements OrderListManager{

          private AuditManager auditManager;

          public Long createOrderList
           (OrderList orderList){
                  Long orderId = orderListDAO.
                          createOrderList(orderList);
                 auditManager.log(new AuditObject
                          (ORDER + orderId, CREATE));
           
                  return orderId;
          }

          public void addLineItem
           (Long orderId, LineItem lineItem)
                  throws FacadeException{

                  Long lineItemId = orderListDAO.
                          addLineItem(orderId, lineItem);
                 auditManager.log(new AuditObject
                          (LINE_ITEM + lineItemId, CREATE));
           
                  int numberOfLineItems = orderListDAO.
                          queryNumberOfLineItems(orderId);
                  if(numberOfLineItems > 2){
                          log("Added LineItem " + lineItemId +
                                  " to Order " + orderId + ";
                                  But rolling back *** !");
                          throw new FacadeException("Make a new
                                  Order for this line item");
                  }
                  else{
                          log("Added LineItem " + lineItemId +
                                  " to Order " + orderId + ".");
                  }
          }

          //其他代碼在這里

          }

            要創(chuàng)建一個這個試驗的異常,我們已經(jīng)介紹了其他事務(wù)規(guī)則規(guī)定一個特定的次序不能在同一行里包含兩個項目。我們應(yīng)該注意到 createOrderList 和 addLineItem調(diào)用了auditManager.log() 方法。你應(yīng)該也注意到了上面方法中的事務(wù)屬性。

          <bean id="orderListManager"
                  class="org.springframework.transaction.
                   interceptor.TransactionProxyFactoryBean">
                  <property name="transactionAttributes">
                          <props>
          <prop key="createOrderList">
                                          PROPAGATION_REQUIRED
                                  </prop>
                                  <prop key="addLineItem">
                                          PROPAGATION_REQUIRED,-com.
                                          example.exception.FacadeException
                                  </prop>
           
                          </props>
                  </property>
          </bean>

          <bean id="auditManager" class="org.
                  springframework.transaction.interceptor.
                          TransactionProxyFactoryBean">
                  <property name="transactionAttributes">
                          <props>
          <prop key="log">
                                          PROPAGATION_REQUIRES_NEW
                                  </prop>
           
                          </props>
                  </property>
          </bean>

            PROPAGATION_REQUIRED 和 TX_REQUIRED相同,而PROPAGATION_REQUIRES_NEW 和在EJB里的 TX_REQUIRES_NEW 相同。如果我們想讓方法一直在事務(wù)里運(yùn)行,可以用PROPAGATION_REQUIRED。這時,如果有一個TX已經(jīng)運(yùn)行了,bean的方法就會加入到 TX里,或者Spring的TX管理器給你新建一個。如果我們想一旦方法被調(diào)用,就創(chuàng)建一個新的事務(wù)實例,我們可以用 PROPAGATION_REQUIRES_NEW 屬性。

            我們同樣要讓addLineItem 一直都在拋出FacadeException異常時回滾事務(wù)。在我們有異常的情況下,使得我們可以很好的控制TX結(jié)束達(dá)到了另一個級別。前綴-符號表示回滾TX,而+ 符號表示提交TX。

            接下來的問題是為什么我們給log方法設(shè)置一個PROPAGATION_REQUIRES_NEW呢?這是因為我們要做的是無論主函數(shù)怎么運(yùn)行,我們都要為所有訂單的創(chuàng)建和項目的添加記錄審核情況。就是說即使在運(yùn)行createOrderList 和 addLineItem 的時候拋出了異常也要記錄。這僅在我們開始一個新的TX并調(diào)用log的時候起作用。這就是為什么要給它設(shè)置的原因:

            如果調(diào)用auditManager.log(new AuditObject(LINE_ITEM +

            lineItemId, CREATE));成功了, auditManager.log() 將在新的TX里發(fā)生,這樣auditManager.log() 成功的話就會被提交 (前提是不拋出異常)。

            設(shè)置試驗工程的環(huán)境

            為了運(yùn)行這個工程,我們來按Spring Live 這本書的流程進(jìn)行:

            1. 下載并安裝以下組件,注意版本,不然會引起一系列的問題

            o JDK 1_5_0_01 或更高
            o Apache Tomcat 5.5.7
            o Apache Ant 1.6.2
            o Equinox 1.2

            2. 配置系統(tǒng)環(huán)境變量:

            o JAVA_HOME
            o CATALINA_HOME
            o ANT_HOME

            3. 把下列目錄添加到你的PATH變量中去或者用絕對路徑來運(yùn)行你的程序:

            o JAVA_HOME\bin
            o CATALINA_HOME\bin
            o ANT_HOME\bin

            4. 要配置Tomcat, 打開 $CATALINA_HOME/conf/tomcat-users.xml 確保里面有下面這段文字,如果沒有就手動添加進(jìn)去:

          <role rolename="manager"/><user username="admin" password="admin" roles="manager"/>  

            5. 要創(chuàng)建基于Struts,Spring, 和 Hibernate的web工程,我們要用Equinox來構(gòu)建一個空白的框架,這將包含上面提到的文件結(jié)構(gòu),所有需要用到的jar文件,還有ant構(gòu)建腳本。把Equinox解壓到一個文件夾中,將創(chuàng)建一個equinox文件夾。到equinox文件夾里去,輸入命令A(yù)NT_HOME\bin\ant new -Dapp.name=myusers。這樣就會創(chuàng)建一個和equinox結(jié)構(gòu)一樣的文件夾myusers 。具體內(nèi)容如下:

            圖 4. Equinox myusers 工程文件夾

            6. 刪掉myusers\web\WEB-INF目錄下所有的xml文件。

            7. 復(fù)制 equinox\extras\struts\web\WEB-INF\lib\struts*.jar 到 myusers\web\WEB-INF\lib,這樣這個工程就可以用struts了。

            8. 用本文最后的資源里的代碼, 解壓myusersextra.zip 到相應(yīng)位置。 把目錄下的所有內(nèi)容拷貝到myusers目錄下。

            9. 打開命令行轉(zhuǎn)到myusers目錄下。輸入CATALINA_HOME\bin\startup 從myusers 目錄啟動Tomcat可以保證數(shù)據(jù)庫在myusers 文件夾里創(chuàng)建,這樣可以避免在運(yùn)行build.xml里定義的任務(wù)發(fā)生錯誤。

            10. 再次打開命令行轉(zhuǎn)到myusers。執(zhí)行 Execute ANT_HOME\bin\ant install。這樣將創(chuàng)建整個工程并把它部署到Tomcat里。這時我們可以看到myusers 里多了一個 db 目錄,里面存放數(shù)據(jù)庫 appfuse1 和 appfuse2。

            11. 打開瀏覽器確定myusers 工程部署在http://localhost:8080/myusers/

            12. 要重建工程,執(zhí)行ANT_HOME\bin\ant remove,用CATALINA_HOME\bin\shutdown關(guān)掉Tomcat.并在CATALINA_HOME\webapps里刪掉 myusers 文件夾。然后用CATALINA_HOME\bin\startup 重啟Tomcat然后用ANT_HOME\bin\ant install重構(gòu)工程。

            執(zhí)行工程

            如果要測試,OrderListManagerTest,在myusers\test\com\example\service 目錄下可以運(yùn)行作為JUnit測試。要運(yùn)行的話,在構(gòu)建工程時加入以下代碼:

          CATALINA_HOME\bin\ant test -Dtestcase=OrderListManager

            測試工程分為兩個主要部分:第一個部分創(chuàng)建兩行項目的一個排列,并把這兩個鏈接到排列中。如下所示,可以成功運(yùn)行:

          OrderList orderList1 = new OrderList();
          Long orderId1 = orderListManager.
                  createOrderList(orderList1);
          log("Created OrderList with id '"
                  + orderId1 + "'...");
          orderListManager.addLineItem(orderId1,lineItem1);
          orderListManager.addLineItem(orderId1,lineItem2);

            另一個執(zhí)行類似的事件,但是這時我們添加三行到排列中去,將產(chǎn)生一個異常

          OrderList orderList2 = new OrderList();
          Long orderId2 = orderListManager.
                  createOrderList(orderList2);
          log("Created OrderList with id '"
                  + orderId2 + "'...");
          orderListManager.addLineItem(orderId2,lineItem3);
          orderListManager.addLineItem(orderId2,lineItem4);
          //這里將拋出異常…………但是仍然要執(zhí)行下去
          try{
            orderListManager.addLineItem
                  (orderId2,lineItem5);
          }
          catch(FacadeException facadeException){
            log("ERROR : " + facadeException.getMessage());
          }

            輸出窗口如圖5所示:

            圖 5. 客戶端輸出

            我們創(chuàng)建了Order1,添加了兩個ID是1和2的項目到里面去。然后創(chuàng)建Order2, 試圖添加3個項目,前兩個(ID是3和4)成功了,如圖5所示添加ID為5的項目時拋出了異常。然后,事務(wù)回滾,數(shù)據(jù)庫里沒有ID為5的項目。執(zhí)行以下代碼從圖6和圖7可以看出:

            CATALINA_HOME\bin\ant browse1

            圖 6. appfuse1 數(shù)據(jù)庫里的排列

           

            圖 7. appfuse1里的項目

            接下來,試驗中可以看出次序和項目存在appfuse1 里,而審核部分在appfuse2里. OrderListManager 同時訪問兩個數(shù)據(jù)庫。打開 appfuse2 數(shù)據(jù)庫,看審核記錄的細(xì)節(jié):

            CATALINA_HOME\bin\ant browse2

            圖 8. appfuse2數(shù)據(jù)庫里的審核記錄, 包括失敗的TX

            表8最后一列尤其值得注意,RESOURCE這一欄上顯示這一行對應(yīng)著LineItem5。 但是當(dāng)我們回過來看圖7,卻發(fā)現(xiàn)并沒有這種對應(yīng)。這是個錯誤嗎?事實上,沒有問題,圖7里沒有的那行其實是這篇文章的精華所在,讓我們來看看是怎么回事。

            首先addLineItem() 方法有 PROPAGATION_REQUIRED 屬性而 log() 方法有PROPAGATION_REQUIRES_NEW。進(jìn)而, addLineItem() 在內(nèi)部調(diào)用log() 方法。所以我們往第二個排列里添加第三個表項時,發(fā)生了異常 (由于我們的事務(wù)規(guī)則),就將這個創(chuàng)建過程和鏈接都回滾了。但是,因為已經(jīng)調(diào)用了log(),而log()有 PROPAGATION_REQUIRES_NEW TX 屬性,回滾了addLineItem() 不會回滾 log(), 因為 log() 是在一個新的TX里。

            讓我們現(xiàn)在改變一下log()的TX屬性。把PROPAGATION_REQUIRES_NEW 替換成PROPAGATION_SUPPORTS。ROPAGATION_SUPPORTS 屬性允許方法在客戶端的TX里運(yùn)行,如果客戶端有TX,否則就不用TX。你需要重建工程讓這些變化自動被刷新。請按照設(shè)置工程環(huán)境的第12步。

            重新開始的話,我們會發(fā)現(xiàn)有一點(diǎn)不同。這次,我們在往排列2添加第三項時依然有異常。發(fā)生回滾。這時方法調(diào)用了log()方法。由于它有著 PROPAGATION_SUPPORTSTX屬性, log() 將在同一個addLineItem() 方法環(huán)境下調(diào)用。由于 addLineItem() 回滾,log() 也回滾了,沒有留下審核記錄。所以在圖9里沒有這項失敗的記錄!

            Figure 9. appfuse2數(shù)據(jù)庫的審核記錄,沒有失敗的TX

            我們所改變的僅僅是TX屬性,如下所示:

          <bean id="auditManager"
                  class="org.springframework.transaction.
                  interceptor.TransactionProxyFactoryBean">
              <property name="transactionAttributes">
                  <props>
          <!-- prop key="log">
                          PROPAGATION_REQUIRES_NEW
                      </prop -->
                      <prop key="log">
                          PROPAGATION_SUPPORTS
                      </prop>
           
                  </props>
              </property>
          </bean>

            這是生成實例管理的效果,自從我們接觸EJB以來就開始尋找杠桿的最佳位置。我們需要一個高端的應(yīng)用服務(wù)器 來管理我們的our EJB組件。現(xiàn)在我們不用EJB服務(wù)器就達(dá)到了一樣的結(jié)果,用Spring。

            這篇文章介紹了J2EE里十分強(qiáng)大的組合之一:Spring 和 Hibernate。通過兩者的有機(jī)結(jié)合,我們現(xiàn)在多了對Container-Managed Persistence (CMP), Container-Managed Relationships (CMR), 和生成實例管理的新選擇。雖然Spring不能完全代替EJB,但是它提供了很多功能,例如一般Java程序的實例生成,使得用戶可以在大部分工程中和 EJB搭配使用。

            我們不是要為了尋找EJB的代替品,而是對于現(xiàn)在的問題得出一個最理想的解決方案。我們?nèi)匀灰獙ふ襍pring 和 Hibernate組合的更多優(yōu)點(diǎn),這就留給我們的讀者去探索了。

          posted on 2006-02-24 10:24 靜夜思 閱讀(664) 評論(1)  編輯  收藏 所屬分類: 開源軟件

          評論

          # re: 在Spring中集成Hibernate事務(wù) 2007-09-02 17:04 11

          抄來抄去的,沒有意思  回復(fù)  更多評論   

          主站蜘蛛池模板: 三河市| 临高县| 固始县| 视频| 宁强县| 信宜市| 淳化县| 开封县| 九寨沟县| 安仁县| 广元市| 资中县| 永州市| 赣州市| 镇沅| 大邑县| 永德县| 镇康县| 武鸣县| 许昌县| 浦城县| 镇原县| 黄平县| 集贤县| 陕西省| 长岛县| 潞西市| 宁安市| 儋州市| 景宁| 南平市| 梁平县| 庄浪县| 科技| 新密市| 厦门市| 烟台市| 大丰市| 淳安县| 土默特右旗| 图木舒克市|