閱讀32 返回首頁    go 京東網上商城


設計模式之事務處理

 事務處理是企業應用需要解決的最主要的問題之一。J2EE通過JTA提供了完整的事務管理能力,包括多個事務性資源的管理能力。但是大部分應用都是運行在單一的事務性資源之上(一個數據庫),他們並不需要全局性的事務服務。本地事務服務已然足夠(比如JDBC事務管理)。
    本文並不討論應該采用何種事務處理方式,主要目的是討論如何更為優雅地設計事務服務。僅以JDBC事務處理為例。涉及到的DAO,Factory,Proxy,Decorator等模式概念,請閱讀相關資料。
    也許你聽說過,事務處理應該做在service層,也許你也正這樣做,但是否知道為什麼這樣做?為什麼不放在DAO層做事務處理。顯而易見的原因是業務層接口的每一個方法有時候都是一個業務用例(User Case),它需要調用不同的DAO對象來完成一個業務方法。比如簡單地以網上書店購書最後的確定定單為例,業務方法首先是調用BookDAO對象(一般是通過DAO工廠產生),BookDAO判斷是否還有庫存餘量,取得該書的價格信息等,然後調用CustomerDAO從帳戶扣除相應的費用以及記錄信息,然後是其他服務(通知管理員等)。簡化業務流程大概如此:

    首先是業務接口,針對接口,而不是針對類編程:
ExpandedBlockStart.gifpublic interface BookStoreManager{
InBlock.gif          public boolean buyBook(String bookId,int quantity)throws SystemException;
InBlock.gif          dot.gif.其他業務方法
ExpandedBlockEnd.gif}

None.gif

    接下來就是業務接口的實現類——業務對象:
ExpandedBlockStart.gif   public class BookStoreManagerImpl implements BookStoreManager{
ExpandedSubBlockStart.gif         public boolean buyBook(String bookId)throws SystemException{
InBlock.gif              Connection conn=ConnectionManager.getConnection();//獲取數據庫連接
InBlock.gif
              boolean b=false;
InBlock.gif              
ExpandedSubBlockStart.gif              try{
InBlock.gif                  conn.setAutoCommit(false);  //取消自動提交
InBlock.gif
                  BookDAO bookDAO=DAOFactory.getBookDAO();
InBlock.gif                  CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
InBlock.gif                    //嚐試從庫存中取書 
ExpandedSubBlockStart.gif
                  if(BookDAO.reduceInventory(conn,bookId,quantity)){
InBlock.gif                       BigDecimal price=BookDAO.getPrice(bookId);  //取價格
InBlock.gif                       
//從客戶帳戶中扣除price*quantity的費用
InBlock.gif
                       b=
InBlock.gif                       CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
InBlock.gif                       dot.gif.
InBlock.gif                       其他業務方法,如通知管理員,生成定單等.
InBlock.gif                        dot.gif
InBlock.gif                       conn.commit();   //提交事務
InBlock.gif
                       conn.setAutoCommit(true);
ExpandedSubBlockEnd.gif                  }

ExpandedSubBlockStart.gif               }
catch(SQLException e){
InBlock.gif                  conn.rollback();   //出現異常,回滾事務
InBlock.gif
                  con.setAutoCommit(true);
InBlock.gif                  e.printStackTrace();
InBlock.gif                  throws new SystemException(e);   
ExpandedSubBlockEnd.gif               }

InBlock.gif               return b;
ExpandedSubBlockEnd.gif         }
 
ExpandedBlockEnd.gif    }

None.gif
 
    然後是業務代表工廠:
  
ExpandedBlockStart.gif public final class ManagerFactory {
ExpandedSubBlockStart.gif      public static BookStoreManager getBookStoreManager() {
InBlock.gif         return new BookStoreManagerImpl();
ExpandedSubBlockEnd.gif      }

ExpandedBlockEnd.gif   }

None.gif


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

    我們有更為優雅的設計來解決這類問題,如果我們把Transaction Context的控製交給一個被業務代表對象、DAO和其他Component所共知的外部對象。當業務代表對象的某個方法需要事務管理時,它提示此外部對象它希望開始一個事務,外部對象獲取一個連接並且開始數據庫事務。也就是將事務控製從service層抽離,當web層調用service層的某個業務代表對象時,返回的是一個經過Transaction Context外部對象包裝(或者說代理)的業務對象。此代理對象將請求發送給原始業務代表對象,但是對其中的業務方法進行事務控製。那麼,我們如何實現此效果呢?答案是JDK1.3引進的動態代理技術。動態代理技術隻能代理接口,這也是為什麼我們需要業務接口BookStoreManager的原因。
    首先,我們引入這個Transaction Context外部對象,它的代碼其實很簡單,如果不了解動態代理技術的請先閱讀其他資料。
None.gifimport java.lang.reflect.InvocationHandler;
None.gifimport java.lang.reflect.Method;
None.gifimport java.lang.reflect.Proxy;
None.gif
None.gifimport java.sql.Connection;
None.gif
None.gifimport com.strutslet.demo.service.SystemException;
None.gif
ExpandedBlockStart.gifpublic final class TransactionWrapper {
InBlock.gif
ExpandedSubBlockStart.gif    /**
InBlock.gif     * 裝飾原始的業務代表對象,返回一個與業務代表對象有相同接口的代理對象 
ExpandedSubBlockEnd.gif     
*/

ExpandedSubBlockStart.gif    public static Object decorate(Object delegate) {
InBlock.gif        return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
InBlock.gif                delegate.getClass().getInterfaces(), new XAWrapperHandler(
InBlock.gif                        delegate));
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    //動態代理技術
ExpandedSubBlockStart.gif
    static final class XAWrapperHandler implements InvocationHandler {
InBlock.gif        private final Object delegate;
InBlock.gif
ExpandedSubBlockStart.gif        XAWrapperHandler(Object delegate) {
InBlock.gif           this.delegate = delegate;
ExpandedSubBlockEnd.gif        }

InBlock.gif        
InBlock.gif        //簡單起見,包裝業務代表對象所有的業務方法
InBlock.gif
        public Object invoke(Object proxy, Method method, Object[] args)
ExpandedSubBlockStart.gif                throws Throwable {
InBlock.gif            Object result = null;
InBlock.gif            Connection con = ConnectionManager.getConnection();
ExpandedSubBlockStart.gif            try 
InBlock.gif                //開始一個事務
InBlock.gif
                con.setAutoCommit(false);
InBlock.gif                //調用原始業務對象的業務方法
InBlock.gif
                result = method.invoke(delegate, args);
InBlock.gif                con.commit();   //提交事務
InBlock.gif
                con.setAutoCommit(true);
ExpandedSubBlockStart.gif            }
 catch (Throwable t) {
InBlock.gif                //回滾
InBlock.gif
                con.rollback();
InBlock.gif                con.setAutoCommit(true);
InBlock.gif                throw new SystemException(t);
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            return result;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

    正如我們所見,此對象隻不過把業務對象需要事務控製的業務方法中的事務控製部分抽取出來而已。請注意,業務代表對象內部調用自身的方法將不會開始新的事務,因為這些調用不會傳給代理對象。如此,我們去除了代表重複的味道。此時,我們的業務代表對象修改成:
ExpandedBlockStart.gifpublic class BookStoreManagerImpl implements BookStoreManager {
ExpandedSubBlockStart.gif    public boolean buyBook(String bookId)throws SystemException{
InBlock.gif          Connection conn=ConnectionManager.getConnection();// 獲取數據庫連接
InBlock.gif
          boolean b=false;
ExpandedSubBlockStart.gif          try{
InBlock.gif              BookDAO bookDAO=DAOFactory.getBookDAO();
InBlock.gif              CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
InBlock.gif              // 嚐試從庫存中取書
ExpandedSubBlockStart.gif
              if(BookDAO.reduceInventory(conn,bookId,quantity)){
InBlock.gif                  BigDecimal price=BookDAO.getPrice(bookId);  // 取價格
InBlock.gif                  
// 從客戶帳戶中扣除price*quantity的費用
InBlock.gif
                  b=
InBlock.gif                  CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
InBlock.gif                  dot.gif.
InBlock.gif                  其他業務方法,如通知管理員,生成定單等.
InBlock.gif                  dot.gif
ExpandedSubBlockEnd.gif              }

ExpandedSubBlockStart.gif          }
catch(SQLException e){
InBlock.gif             throws new SystemException(e);
ExpandedSubBlockEnd.gif          }

InBlock.gif          return b;
ExpandedSubBlockEnd.gif    }

InBlock.gif    dot.gif
ExpandedBlockEnd.gif}

None.gif

    可以看到,此時的業務代表對象專注於實現業務邏輯,它不再關心事務控製細節,把它們全部委托給了外部對象。業務代表工廠也修改一下,讓它返回兩種類型的業務代表對象:
  
ExpandedBlockStart.gif public final class ManagerFactory {
InBlock.gif      //返回一個被包裝的對象,有事務控製能力
ExpandedSubBlockStart.gif
      public static BookStoreManager getBookStoreManagerTrans() {
InBlock.gif          return (BookStoreManager) TransactionWrapper
InBlock.gif                  .decorate(new BookStoreManagerImpl());
ExpandedSubBlockEnd.gif      }

InBlock.gif      //原始版本
ExpandedSubBlockStart.gif
      public static BookStoreManager getBookStoreManager() {
InBlock.gif         return new BookStoreManagerImpl();
ExpandedSubBlockEnd.gif      }

InBlock.gif      dot.gifdot.gif
ExpandedBlockEnd.gif   }

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



最後更新:2017-05-17 00:46:54

  上一篇:go  輕盈與優雅同在:哪款MacBook最值得購買?
  下一篇:go  從一到六談起,聊聊六款值得購買的遊戲