設(shè)計(jì)模式之事務(wù)處理
Posted on 2007-02-06 11:32 dennis 閱讀(2316) 評(píng)論(4) 編輯 收藏 所屬分類: 模式與架構(gòu)??? 事務(wù)處理是企業(yè)應(yīng)用需要解決的最主要的問(wèn)題之一。J2EE通過(guò)JTA提供了完整的事務(wù)管理能力,包括多個(gè)事務(wù)性資源的管理能力。但是大部分應(yīng)用都是運(yùn)行在單一的事務(wù)性資源之上(一個(gè)數(shù)據(jù)庫(kù)),他們并不需要全局性的事務(wù)服務(wù)。本地事務(wù)服務(wù)已然足夠(比如JDBC事務(wù)管理)。
??? 本文并不討論應(yīng)該采用何種事務(wù)處理方式,主要目的是討論如何更為優(yōu)雅地設(shè)計(jì)事務(wù)服務(wù)。僅以JDBC事務(wù)處理為例。涉及到的DAO,F(xiàn)actory,Proxy,Decorator等模式概念,請(qǐng)閱讀相關(guān)資料。
??? 也許你聽(tīng)說(shuō)過(guò),事務(wù)處理應(yīng)該做在service層,也許你也正這樣做,但是否知道為什么這樣做?為什么不放在DAO層做事務(wù)處理。顯而易見(jiàn)的原因是業(yè)務(wù)層接口的每一個(gè)方法有時(shí)候都是一個(gè)業(yè)務(wù)用例(User Case),它需要調(diào)用不同的DAO對(duì)象來(lái)完成一個(gè)業(yè)務(wù)方法。比如簡(jiǎn)單地以網(wǎng)上書店購(gòu)書最后的確定定單為例,業(yè)務(wù)方法首先是調(diào)用BookDAO對(duì)象(一般是通過(guò)DAO工廠產(chǎn)生),BookDAO判斷是否還有庫(kù)存余量,取得該書的價(jià)格信息等,然后調(diào)用CustomerDAO從帳戶扣除相應(yīng)的費(fèi)用以及記錄信息,然后是其他服務(wù)(通知管理員等)。簡(jiǎn)化業(yè)務(wù)流程大概如此:
??? 首先是業(yè)務(wù)接口,針對(duì)接口,而不是針對(duì)類編程:

public?interface?BookStoreManager
{
??????????public?boolean?buyBook(String?bookId,int?quantity)throws?SystemException;
??????????
.其他業(yè)務(wù)方法
}

??? 接下來(lái)就是業(yè)務(wù)接口的實(shí)現(xiàn)類——業(yè)務(wù)對(duì)象:

???public?class?BookStoreManagerImpl?implements?BookStoreManager
{

?????????public?boolean?buyBook(String?bookId)throws?SystemException
{
??????????????Connection?conn=ConnectionManager.getConnection();//獲取數(shù)據(jù)庫(kù)連接
??????????????boolean?b=false;
??????????????

??????????????try
{
??????????????????conn.setAutoCommit(false);??//取消自動(dòng)提交
??????????????????BookDAO?bookDAO=DAOFactory.getBookDAO();
??????????????????CustomerDAO?customerDAO=DAOFactory.getCustomerDAO();
????????????????????//嘗試從庫(kù)存中取書?

??????????????????if(BookDAO.reduceInventory(conn,bookId,quantity))
{
???????????????????????BigDecimal?price=BookDAO.getPrice(bookId);??//取價(jià)格
???????????????????????//從客戶帳戶中扣除price*quantity的費(fèi)用
???????????????????????b=
???????????????????????CustomerDAO.reduceAccount(conn,price.multiply(new?BigDecimal(quantity));
???????????????????????
.
???????????????????????其他業(yè)務(wù)方法,如通知管理員,生成定單等.
????????????????????????
???????????????????????conn.commit();???//提交事務(wù)
???????????????????????conn.setAutoCommit(true);
??????????????????}

???????????????}catch(SQLException?e)
{
??????????????????conn.rollback();???//出現(xiàn)異常,回滾事務(wù)
??????????????????con.setAutoCommit(true);
??????????????????e.printStackTrace();
??????????????????throws?new?SystemException(e);???
???????????????}
???????????????return?b;
?????????}?
????}
?
??? 然后是業(yè)務(wù)代表工廠:
??
?public?final?class?ManagerFactory?
{

??????public?static?BookStoreManager?getBookStoreManager()?
{
?????????return?new?BookStoreManagerImpl();
??????}
???}

??? 這樣的設(shè)計(jì)非常適合于DAO中的簡(jiǎn)單活動(dòng),我們項(xiàng)目中的一個(gè)小系統(tǒng)也是采用這樣的設(shè)計(jì)方案,但是它不適合于更大規(guī)模的應(yīng)用。首先,你有沒(méi)有聞到代碼重復(fù)的 bad smell?每次都要設(shè)置AutoCommit為false,然后提交,出現(xiàn)異常回滾,包裝異常拋到上層,寫多了不煩才怪,那能不能消除呢?其次,業(yè)務(wù)代表對(duì)象現(xiàn)在知道它內(nèi)部事務(wù)管理的所有的細(xì)節(jié),這與我們?cè)O(shè)計(jì)業(yè)務(wù)代表對(duì)象的初衷不符。對(duì)于業(yè)務(wù)代表對(duì)象來(lái)說(shuō),了解一個(gè)與事務(wù)有關(guān)的業(yè)務(wù)約束是相當(dāng)恰當(dāng)?shù)模亲屗?fù)責(zé)來(lái)實(shí)現(xiàn)它們就不太恰當(dāng)了。再次,你是否想過(guò)嵌套業(yè)務(wù)對(duì)象的場(chǎng)景?業(yè)務(wù)代表對(duì)象之間的互相調(diào)用,層層嵌套,此時(shí)你又如何處理呢?你要知道按我們現(xiàn)在的方式,每個(gè)業(yè)務(wù)方法都處于各自獨(dú)立的事務(wù)上下文當(dāng)中(Transaction Context),互相調(diào)用形成了嵌套事務(wù),此時(shí)你又該如何處理?也許辦法就是重新寫一遍,把不同的業(yè)務(wù)方法集中成一個(gè)巨無(wú)霸包裝在一個(gè)事務(wù)上下文中。
??? 我們有更為優(yōu)雅的設(shè)計(jì)來(lái)解決這類問(wèn)題,如果我們把Transaction Context的控制交給一個(gè)被業(yè)務(wù)代表對(duì)象、DAO和其他Component所共知的外部對(duì)象。當(dāng)業(yè)務(wù)代表對(duì)象的某個(gè)方法需要事務(wù)管理時(shí),它提示此外部對(duì)象它希望開(kāi)始一個(gè)事務(wù),外部對(duì)象獲取一個(gè)連接并且開(kāi)始數(shù)據(jù)庫(kù)事務(wù)。也就是將事務(wù)控制從service層抽離,當(dāng)web層調(diào)用service層的某個(gè)業(yè)務(wù)代表對(duì)象時(shí),返回的是一個(gè)經(jīng)過(guò)Transaction Context外部對(duì)象包裝(或者說(shuō)代理)的業(yè)務(wù)對(duì)象。此代理對(duì)象將請(qǐng)求發(fā)送給原始業(yè)務(wù)代表對(duì)象,但是對(duì)其中的業(yè)務(wù)方法進(jìn)行事務(wù)控制。那么,我們?nèi)绾螌?shí)現(xiàn)此效果呢?答案是JDK1.3引進(jìn)的動(dòng)態(tài)代理技術(shù)。動(dòng)態(tài)代理技術(shù)只能代理接口,這也是為什么我們需要業(yè)務(wù)接口BookStoreManager的原因。
??? 首先,我們引入這個(gè)Transaction Context外部對(duì)象,它的代碼其實(shí)很簡(jiǎn)單,如果不了解動(dòng)態(tài)代理技術(shù)的請(qǐng)先閱讀其他資料。
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?
{


????/**?*//**
?????*?裝飾原始的業(yè)務(wù)代表對(duì)象,返回一個(gè)與業(yè)務(wù)代表對(duì)象有相同接口的代理對(duì)象?
?????*/

????public?static?Object?decorate(Object?delegate)?
{
????????return?Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
????????????????delegate.getClass().getInterfaces(),?new?XAWrapperHandler(
????????????????????????delegate));
????}
????
????//動(dòng)態(tài)代理技術(shù)

????static?final?class?XAWrapperHandler?implements?InvocationHandler?
{
????????private?final?Object?delegate;


????????XAWrapperHandler(Object?delegate)?
{
???????????this.delegate?=?delegate;
????????}
????????
????????//簡(jiǎn)單起見(jiàn),包裝業(yè)務(wù)代表對(duì)象所有的業(yè)務(wù)方法
????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)

????????????????throws?Throwable?
{
????????????Object?result?=?null;
????????????Connection?con?=?ConnectionManager.getConnection();

????????????try?
{?
????????????????//開(kāi)始一個(gè)事務(wù)
????????????????con.setAutoCommit(false);
????????????????//調(diào)用原始業(yè)務(wù)對(duì)象的業(yè)務(wù)方法
????????????????result?=?method.invoke(delegate,?args);
????????????????con.commit();???//提交事務(wù)
????????????????con.setAutoCommit(true);

????????????}?catch?(Throwable?t)?
{
????????????????//回滾
????????????????con.rollback();
????????????????con.setAutoCommit(true);
????????????????throw?new?SystemException(t);
????????????}

????????????return?result;
????????}
????}
}

??? 正如我們所見(jiàn),此對(duì)象只不過(guò)把業(yè)務(wù)對(duì)象需要事務(wù)控制的業(yè)務(wù)方法中的事務(wù)控制部分抽取出來(lái)而已。請(qǐng)注意,業(yè)務(wù)代表對(duì)象內(nèi)部調(diào)用自身的方法將不會(huì)開(kāi)始新的事務(wù),因?yàn)檫@些調(diào)用不會(huì)傳給代理對(duì)象。如此,我們?nèi)コ舜碇貜?fù)的味道。此時(shí),我們的業(yè)務(wù)代表對(duì)象修改成:

public?class?BookStoreManagerImpl?implements?BookStoreManager?
{

????public?boolean?buyBook(String?bookId)throws?SystemException
{
??????????Connection?conn=ConnectionManager.getConnection();//?獲取數(shù)據(jù)庫(kù)連接
??????????boolean?b=false;

??????????try
{
??????????????BookDAO?bookDAO=DAOFactory.getBookDAO();
??????????????CustomerDAO?customerDAO=DAOFactory.getCustomerDAO();
??????????????//?嘗試從庫(kù)存中取書

??????????????if(BookDAO.reduceInventory(conn,bookId,quantity))
{
??????????????????BigDecimal?price=BookDAO.getPrice(bookId);??//?取價(jià)格
??????????????????//?從客戶帳戶中扣除price*quantity的費(fèi)用
??????????????????b=
??????????????????CustomerDAO.reduceAccount(conn,price.multiply(new?BigDecimal(quantity));
??????????????????
.
??????????????????其他業(yè)務(wù)方法,如通知管理員,生成定單等.
??????????????????
??????????????}

??????????}catch(SQLException?e)
{
?????????????throws?new?SystemException(e);
??????????}
??????????return?b;
????}
????
.?
}

??? 可以看到,此時(shí)的業(yè)務(wù)代表對(duì)象專注于實(shí)現(xiàn)業(yè)務(wù)邏輯,它不再關(guān)心事務(wù)控制細(xì)節(jié),把它們?nèi)课薪o了外部對(duì)象。業(yè)務(wù)代表工廠也修改一下,讓它返回兩種類型的業(yè)務(wù)代表對(duì)象:
??
?public?final?class?ManagerFactory?
{
??????//返回一個(gè)被包裝的對(duì)象,有事務(wù)控制能力

??????public?static?BookStoreManager?getBookStoreManagerTrans()?
{
??????????return?(BookStoreManager)?TransactionWrapper
??????????????????.decorate(new?BookStoreManagerImpl());
??????}
??????//原始版本

??????public?static?BookStoreManager?getBookStoreManager()?
{
?????????return?new?BookStoreManagerImpl();
??????}
??????

???}
???
?? 我們?cè)跇I(yè)務(wù)代表工廠上提供了兩種不同的對(duì)象生成方法:一個(gè)用于創(chuàng)建被包裝的對(duì)象,它會(huì)為每次方法調(diào)用創(chuàng)建一個(gè)新的事務(wù);另外一個(gè)用于創(chuàng)建未被包裝的版本,它用于加入到已有的事務(wù)(比如其他業(yè)務(wù)代表對(duì)象的業(yè)務(wù)方法),解決了嵌套業(yè)務(wù)代表對(duì)象的問(wèn)題。
?? 我們的設(shè)計(jì)還不夠優(yōu)雅,比如我們默認(rèn)所有的業(yè)務(wù)代表對(duì)象的方法調(diào)用都將被包裝在一個(gè)Transaction Context。可事實(shí)是很多方法也許并不需要與數(shù)據(jù)庫(kù)打交道,如果我們能配置哪些方法需要事務(wù)聲明,哪些不需要事務(wù)管理就更完美了。解決辦法也很簡(jiǎn)單,一個(gè)XML配置文件來(lái)配置這些,調(diào)用時(shí)判斷即可。說(shuō)到這里,了解spring的大概都會(huì)意識(shí)到這不正是聲明式事務(wù)控制嗎?正是如此,事務(wù)控制就是AOP的一種服務(wù),spring的聲明式事務(wù)管理是通過(guò)AOP實(shí)現(xiàn)的。AOP的實(shí)現(xiàn)方式包括:動(dòng)態(tài)代理技術(shù),字節(jié)碼生成技術(shù)(如CGLIB庫(kù)),java代碼生成(早期EJB采用),修改類裝載器以及源代碼級(jí)別的代碼混合織入(aspectj)等。我們這里就是利用了動(dòng)態(tài)代理技術(shù),只能對(duì)接口代理;對(duì)類的動(dòng)態(tài)代理可以使用cglib庫(kù)。
??? 這篇短文只是介紹下我對(duì)事務(wù)上下文模式以及聲明式事務(wù)管理實(shí)現(xiàn)基本原理的理解,如有錯(cuò)誤,請(qǐng)不吝賜教。我的email:killme2008@gmail.com
??? 本文并不討論應(yīng)該采用何種事務(wù)處理方式,主要目的是討論如何更為優(yōu)雅地設(shè)計(jì)事務(wù)服務(wù)。僅以JDBC事務(wù)處理為例。涉及到的DAO,F(xiàn)actory,Proxy,Decorator等模式概念,請(qǐng)閱讀相關(guān)資料。
??? 也許你聽(tīng)說(shuō)過(guò),事務(wù)處理應(yīng)該做在service層,也許你也正這樣做,但是否知道為什么這樣做?為什么不放在DAO層做事務(wù)處理。顯而易見(jiàn)的原因是業(yè)務(wù)層接口的每一個(gè)方法有時(shí)候都是一個(gè)業(yè)務(wù)用例(User Case),它需要調(diào)用不同的DAO對(duì)象來(lái)完成一個(gè)業(yè)務(wù)方法。比如簡(jiǎn)單地以網(wǎng)上書店購(gòu)書最后的確定定單為例,業(yè)務(wù)方法首先是調(diào)用BookDAO對(duì)象(一般是通過(guò)DAO工廠產(chǎn)生),BookDAO判斷是否還有庫(kù)存余量,取得該書的價(jià)格信息等,然后調(diào)用CustomerDAO從帳戶扣除相應(yīng)的費(fèi)用以及記錄信息,然后是其他服務(wù)(通知管理員等)。簡(jiǎn)化業(yè)務(wù)流程大概如此:
??? 首先是業(yè)務(wù)接口,針對(duì)接口,而不是針對(duì)類編程:








??? 接下來(lái)就是業(yè)務(wù)接口的實(shí)現(xiàn)類——業(yè)務(wù)對(duì)象:











































??? 然后是業(yè)務(wù)代表工廠:
??










??? 這樣的設(shè)計(jì)非常適合于DAO中的簡(jiǎn)單活動(dòng),我們項(xiàng)目中的一個(gè)小系統(tǒng)也是采用這樣的設(shè)計(jì)方案,但是它不適合于更大規(guī)模的應(yīng)用。首先,你有沒(méi)有聞到代碼重復(fù)的 bad smell?每次都要設(shè)置AutoCommit為false,然后提交,出現(xiàn)異常回滾,包裝異常拋到上層,寫多了不煩才怪,那能不能消除呢?其次,業(yè)務(wù)代表對(duì)象現(xiàn)在知道它內(nèi)部事務(wù)管理的所有的細(xì)節(jié),這與我們?cè)O(shè)計(jì)業(yè)務(wù)代表對(duì)象的初衷不符。對(duì)于業(yè)務(wù)代表對(duì)象來(lái)說(shuō),了解一個(gè)與事務(wù)有關(guān)的業(yè)務(wù)約束是相當(dāng)恰當(dāng)?shù)模亲屗?fù)責(zé)來(lái)實(shí)現(xiàn)它們就不太恰當(dāng)了。再次,你是否想過(guò)嵌套業(yè)務(wù)對(duì)象的場(chǎng)景?業(yè)務(wù)代表對(duì)象之間的互相調(diào)用,層層嵌套,此時(shí)你又如何處理呢?你要知道按我們現(xiàn)在的方式,每個(gè)業(yè)務(wù)方法都處于各自獨(dú)立的事務(wù)上下文當(dāng)中(Transaction Context),互相調(diào)用形成了嵌套事務(wù),此時(shí)你又該如何處理?也許辦法就是重新寫一遍,把不同的業(yè)務(wù)方法集中成一個(gè)巨無(wú)霸包裝在一個(gè)事務(wù)上下文中。
??? 我們有更為優(yōu)雅的設(shè)計(jì)來(lái)解決這類問(wèn)題,如果我們把Transaction Context的控制交給一個(gè)被業(yè)務(wù)代表對(duì)象、DAO和其他Component所共知的外部對(duì)象。當(dāng)業(yè)務(wù)代表對(duì)象的某個(gè)方法需要事務(wù)管理時(shí),它提示此外部對(duì)象它希望開(kāi)始一個(gè)事務(wù),外部對(duì)象獲取一個(gè)連接并且開(kāi)始數(shù)據(jù)庫(kù)事務(wù)。也就是將事務(wù)控制從service層抽離,當(dāng)web層調(diào)用service層的某個(gè)業(yè)務(wù)代表對(duì)象時(shí),返回的是一個(gè)經(jīng)過(guò)Transaction Context外部對(duì)象包裝(或者說(shuō)代理)的業(yè)務(wù)對(duì)象。此代理對(duì)象將請(qǐng)求發(fā)送給原始業(yè)務(wù)代表對(duì)象,但是對(duì)其中的業(yè)務(wù)方法進(jìn)行事務(wù)控制。那么,我們?nèi)绾螌?shí)現(xiàn)此效果呢?答案是JDK1.3引進(jìn)的動(dòng)態(tài)代理技術(shù)。動(dòng)態(tài)代理技術(shù)只能代理接口,這也是為什么我們需要業(yè)務(wù)接口BookStoreManager的原因。
??? 首先,我們引入這個(gè)Transaction Context外部對(duì)象,它的代碼其實(shí)很簡(jiǎn)單,如果不了解動(dòng)態(tài)代理技術(shù)的請(qǐng)先閱讀其他資料。


































































??? 正如我們所見(jiàn),此對(duì)象只不過(guò)把業(yè)務(wù)對(duì)象需要事務(wù)控制的業(yè)務(wù)方法中的事務(wù)控制部分抽取出來(lái)而已。請(qǐng)注意,業(yè)務(wù)代表對(duì)象內(nèi)部調(diào)用自身的方法將不會(huì)開(kāi)始新的事務(wù),因?yàn)檫@些調(diào)用不會(huì)傳給代理對(duì)象。如此,我們?nèi)コ舜碇貜?fù)的味道。此時(shí),我們的業(yè)務(wù)代表對(duì)象修改成:






































??? 可以看到,此時(shí)的業(yè)務(wù)代表對(duì)象專注于實(shí)現(xiàn)業(yè)務(wù)邏輯,它不再關(guān)心事務(wù)控制細(xì)節(jié),把它們?nèi)课薪o了外部對(duì)象。業(yè)務(wù)代表工廠也修改一下,讓它返回兩種類型的業(yè)務(wù)代表對(duì)象:
??





















?? 我們?cè)跇I(yè)務(wù)代表工廠上提供了兩種不同的對(duì)象生成方法:一個(gè)用于創(chuàng)建被包裝的對(duì)象,它會(huì)為每次方法調(diào)用創(chuàng)建一個(gè)新的事務(wù);另外一個(gè)用于創(chuàng)建未被包裝的版本,它用于加入到已有的事務(wù)(比如其他業(yè)務(wù)代表對(duì)象的業(yè)務(wù)方法),解決了嵌套業(yè)務(wù)代表對(duì)象的問(wèn)題。
?? 我們的設(shè)計(jì)還不夠優(yōu)雅,比如我們默認(rèn)所有的業(yè)務(wù)代表對(duì)象的方法調(diào)用都將被包裝在一個(gè)Transaction Context。可事實(shí)是很多方法也許并不需要與數(shù)據(jù)庫(kù)打交道,如果我們能配置哪些方法需要事務(wù)聲明,哪些不需要事務(wù)管理就更完美了。解決辦法也很簡(jiǎn)單,一個(gè)XML配置文件來(lái)配置這些,調(diào)用時(shí)判斷即可。說(shuō)到這里,了解spring的大概都會(huì)意識(shí)到這不正是聲明式事務(wù)控制嗎?正是如此,事務(wù)控制就是AOP的一種服務(wù),spring的聲明式事務(wù)管理是通過(guò)AOP實(shí)現(xiàn)的。AOP的實(shí)現(xiàn)方式包括:動(dòng)態(tài)代理技術(shù),字節(jié)碼生成技術(shù)(如CGLIB庫(kù)),java代碼生成(早期EJB采用),修改類裝載器以及源代碼級(jí)別的代碼混合織入(aspectj)等。我們這里就是利用了動(dòng)態(tài)代理技術(shù),只能對(duì)接口代理;對(duì)類的動(dòng)態(tài)代理可以使用cglib庫(kù)。
??? 這篇短文只是介紹下我對(duì)事務(wù)上下文模式以及聲明式事務(wù)管理實(shí)現(xiàn)基本原理的理解,如有錯(cuò)誤,請(qǐng)不吝賜教。我的email:killme2008@gmail.com