天天看點

設計模式之事務處理

 事務處理是企業應用需要解決的最主要的問題之一。j2ee通過jta提供了完整的事務管理能力,包括多個事務性資源的管理能力。但是大部分應用都是運作在單一的事務性資源之上(一個資料庫),他們并不需要全局性的事務服務。本地事務服務已然足夠(比如jdbc事務管理)。

    本文并不讨論應該采用何種事務處理方式,主要目的是讨論如何更為優雅地設計事務服務。僅以jdbc事務處理為例。涉及到的dao,factory,proxy,decorator等模式概念,請閱讀相關資料。

    也許你聽說過,事務處理應該做在service層,也許你也正這樣做,但是否知道為什麼這樣做?為什麼不放在dao層做事務處理。顯而易見的原因是業務層接口的每一個方法有時候都是一個業務用例(user case),它需要調用不同的dao對象來完成一個業務方法。比如簡單地以網上書店購書最後的确定定單為例,業務方法首先是調用bookdao對象(一般是通過dao工廠産生),bookdao判斷是否還有庫存餘量,取得該書的價格資訊等,然後調用customerdao從帳戶扣除相應的費用以及記錄資訊,然後是其他服務(通知管理者等)。簡化業務流程大概如此:

    首先是業務接口,針對接口,而不是針對類程式設計:

設計模式之事務處理

public interface bookstoremanager{

設計模式之事務處理

          public boolean buybook(string bookid,int quantity)throws systemexception;

設計模式之事務處理
設計模式之事務處理

.其他業務方法

設計模式之事務處理

}

設計模式之事務處理

    接下來就是業務接口的實作類——業務對象:

設計模式之事務處理

   public class bookstoremanagerimpl implements bookstoremanager{

設計模式之事務處理

         public boolean buybook(string bookid)throws systemexception{

設計模式之事務處理

              connection conn=connectionmanager.getconnection();//擷取資料庫連接配接

設計模式之事務處理

              boolean b=false;

設計模式之事務處理
設計模式之事務處理

              try{

設計模式之事務處理

                  conn.setautocommit(false);  //取消自動送出

設計模式之事務處理

                  bookdao bookdao=daofactory.getbookdao();

設計模式之事務處理

                  customerdao customerdao=daofactory.getcustomerdao();

設計模式之事務處理

                    //嘗試從庫存中取書 

設計模式之事務處理

                  if(bookdao.reduceinventory(conn,bookid,quantity)){

設計模式之事務處理

                       bigdecimal price=bookdao.getprice(bookid);  //取價格

設計模式之事務處理

                       //從客戶帳戶中扣除price*quantity的費用

設計模式之事務處理

                       b=

設計模式之事務處理

                       customerdao.reduceaccount(conn,price.multiply(new bigdecimal(quantity));

設計模式之事務處理
設計模式之事務處理

.

設計模式之事務處理

                       其他業務方法,如通知管理者,生成定單等.

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

                       conn.commit();   //送出事務

設計模式之事務處理

                       conn.setautocommit(true);

設計模式之事務處理

                  }

設計模式之事務處理

               }catch(sqlexception e){

設計模式之事務處理

                  conn.rollback();   //出現異常,復原事務

設計模式之事務處理

                  con.setautocommit(true);

設計模式之事務處理

                  e.printstacktrace();

設計模式之事務處理

                  throws new systemexception(e);   

設計模式之事務處理

               }

設計模式之事務處理

               return b;

設計模式之事務處理

         } 

設計模式之事務處理

    }

設計模式之事務處理

    然後是業務代表工廠:

設計模式之事務處理

 public final class managerfactory {

設計模式之事務處理

      public static bookstoremanager getbookstoremanager() {

設計模式之事務處理

         return new bookstoremanagerimpl();

設計模式之事務處理

      }

設計模式之事務處理

   }

設計模式之事務處理

    這樣的設計非常适合于dao中的簡單活動,我們項目中的一個小系統也是采用這樣的設計方案,但是它不适合于更大規模的應用。首先,你有沒有聞到代碼重複的 bad smell?每次都要設定autocommit為false,然後送出,出現異常復原,包裝異常抛到上層,寫多了不煩才怪,那能不能消除呢?其次,業務代表對象現在知道它内部事務管理的所有的細節,這與我們設計業務代表對象的初衷不符。對于業務代表對象來說,了解一個與事務有關的業務限制是相當恰當的,但是讓它負責來實作它們就不太恰當了。再次,你是否想過嵌套業務對象的場景?業務代表對象之間的互相調用,層層嵌套,此時你又如何處理呢?你要知道按我們現在的方式,每個業務方法都處于各自獨立的事務上下文當中(transaction context),互相調用形成了嵌套事務,此時你又該如何處理?也許辦法就是重新寫一遍,把不同的業務方法集中成一個巨無霸包裝在一個事務上下文中。

    我們有更為優雅的設計來解決這類問題,如果我們把transaction context的控制交給一個被業務代表對象、dao和其他component所共知的外部對象。當業務代表對象的某個方法需要事務管理時,它提示此外部對象它希望開始一個事務,外部對象擷取一個連接配接并且開始資料庫事務。也就是将事務控制從service層抽離,當web層調用service層的某個業務代表對象時,傳回的是一個經過transaction context外部對象包裝(或者說代理)的業務對象。此代理對象将請求發送給原始業務代表對象,但是對其中的業務方法進行事務控制。那麼,我們如何實作此效果呢?答案是jdk1.3引進的動态代理技術。動态代理技術隻能代理接口,這也是為什麼我們需要業務接口bookstoremanager的原因。

    首先,我們引入這個transaction context外部對象,它的代碼其實很簡單,如果不了解動态代理技術的請先閱讀其他資料。

設計模式之事務處理

import java.lang.reflect.invocationhandler;

設計模式之事務處理

import java.lang.reflect.method;

設計模式之事務處理

import java.lang.reflect.proxy;

設計模式之事務處理
設計模式之事務處理

import java.sql.connection;

設計模式之事務處理
設計模式之事務處理

import com.strutslet.demo.service.systemexception;

設計模式之事務處理
設計模式之事務處理

public final class transactionwrapper {

設計模式之事務處理
設計模式之事務處理

    /**

設計模式之事務處理

     * 裝飾原始的業務代表對象,傳回一個與業務代表對象有相同接口的代理對象 

設計模式之事務處理

     */

設計模式之事務處理

    public static object decorate(object delegate) {

設計模式之事務處理

        return proxy.newproxyinstance(delegate.getclass().getclassloader(),

設計模式之事務處理

                delegate.getclass().getinterfaces(), new xawrapperhandler(

設計模式之事務處理

                        delegate));

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

    //動态代理技術

設計模式之事務處理

    static final class xawrapperhandler implements invocationhandler {

設計模式之事務處理

        private final object delegate;

設計模式之事務處理
設計模式之事務處理

        xawrapperhandler(object delegate) {

設計模式之事務處理

           this.delegate = delegate;

設計模式之事務處理

        }

設計模式之事務處理
設計模式之事務處理

        //簡單起見,包裝業務代表對象所有的業務方法

設計模式之事務處理

        public object invoke(object proxy, method method, object[] args)

設計模式之事務處理

                throws throwable {

設計模式之事務處理

            object result = null;

設計模式之事務處理

            connection con = connectionmanager.getconnection();

設計模式之事務處理

            try { 

設計模式之事務處理

                //開始一個事務

設計模式之事務處理

                con.setautocommit(false);

設計模式之事務處理

                //調用原始業務對象的業務方法

設計模式之事務處理

                result = method.invoke(delegate, args);

設計模式之事務處理

                con.commit();   //送出事務

設計模式之事務處理

                con.setautocommit(true);

設計模式之事務處理

            } catch (throwable t) {

設計模式之事務處理

                //復原

設計模式之事務處理

                con.rollback();

設計模式之事務處理
設計模式之事務處理

                throw new systemexception(t);

設計模式之事務處理

            }

設計模式之事務處理
設計模式之事務處理

            return result;

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

    正如我們所見,此對象隻不過把業務對象需要事務控制的業務方法中的事務控制部分抽取出來而已。請注意,業務代表對象内部調用自身的方法将不會開始新的事務,因為這些調用不會傳給代理對象。如此,我們去除了代表重複的味道。此時,我們的業務代表對象修改成:

設計模式之事務處理

public class bookstoremanagerimpl implements bookstoremanager {

設計模式之事務處理

    public boolean buybook(string bookid)throws systemexception{

設計模式之事務處理

          connection conn=connectionmanager.getconnection();// 擷取資料庫連接配接

設計模式之事務處理

          boolean b=false;

設計模式之事務處理

          try{

設計模式之事務處理

              bookdao bookdao=daofactory.getbookdao();

設計模式之事務處理

              customerdao customerdao=daofactory.getcustomerdao();

設計模式之事務處理

              // 嘗試從庫存中取書

設計模式之事務處理

              if(bookdao.reduceinventory(conn,bookid,quantity)){

設計模式之事務處理

                  bigdecimal price=bookdao.getprice(bookid);  // 取價格

設計模式之事務處理

                  // 從客戶帳戶中扣除price*quantity的費用

設計模式之事務處理

                  b=

設計模式之事務處理

                  customerdao.reduceaccount(conn,price.multiply(new bigdecimal(quantity));

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

                  其他業務方法,如通知管理者,生成定單等.

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

              }

設計模式之事務處理

          }catch(sqlexception e){

設計模式之事務處理

             throws new systemexception(e);

設計模式之事務處理

          }

設計模式之事務處理

          return b;

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

設計模式之事務處理
設計模式之事務處理

    可以看到,此時的業務代表對象專注于實作業務邏輯,它不再關心事務控制細節,把它們全部委托給了外部對象。業務代表工廠也修改一下,讓它傳回兩種類型的業務代表對象:

設計模式之事務處理
設計模式之事務處理

      //傳回一個被包裝的對象,有事務控制能力

設計模式之事務處理

      public static bookstoremanager getbookstoremanagertrans() {

設計模式之事務處理

          return (bookstoremanager) transactionwrapper

設計模式之事務處理

                  .decorate(new bookstoremanagerimpl());

設計模式之事務處理
設計模式之事務處理

      //原始版本

設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理
設計模式之事務處理

   我們在業務代表工廠上提供了兩種不同的對象生成方法:一個用于建立被包裝的對象,它會為每次方法調用建立一個新的事務;另外一個用于建立未被包裝的版本,它用于加入到已有的事務(比如其他業務代表對象的業務方法),解決了嵌套業務代表對象的問題。 

   我們的設計還不夠優雅,比如我們預設所有的業務代表對象的方法調用都将被包裝在一個transaction context。可事實是很多方法也許并不需要與資料庫打交道,如果我們能配置哪些方法需要事務聲明,哪些不需要事務管理就更完美了。解決辦法也很簡單,一個xml配置檔案來配置這些,調用時判斷即可。說到這裡,了解spring的大概都會意識到這不正是聲明式事務控制嗎?正是如此,事務控制就是aop的一種服務,spring的聲明式事務管理是通過aop實作的。aop的實作方式包括:動态代理技術,位元組碼生成技術(如cglib庫),java代碼生成(早期ejb采用),修改類裝載器以及源代碼級别的代碼混合織入(aspectj)等。我們這裡就是利用了動态代理技術,隻能對接口代理;對類的動态代理可以使用cglib庫。

文章轉自莊周夢蝶  ,原文釋出時間2007-02-06