<a href="http://my.oschina.net/pingpangkuangmo/blog/413518">分布式事務系列(開篇)提出疑問和研究過程</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/415162">分布式事務系列(1.1)spring事務管理器platformtransactionmanager源碼分析</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/416038">分布式事務系列(1.2)spring事務體系</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/417479">分布式事務系列(2.1)分布式事務模型與接口定義</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/419374">分布式事務系列(3.1)jotm的分布式案例</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/420831">分布式事務系列(3.2)jotm分布式事務源碼分析</a>
<a href="http://my.oschina.net/pingpangkuangmo/blog/423210">分布式事務系列(4.1)atomikos的分布式案例</a>
三種事務模型如下:
本地事務模型
程式設計式事務模型
聲明式事務模型
先來看幾個例子:
案例1:
案例2:
案例3:
案例4:
我的看法:
案例1屬于本地事務模型
案例2、3屬于程式設計式事務模型
案例4屬于聲明式事務模型
我認為他們各自的特點在于:誰在管理着事務的送出和復原等操作?
這裡有三個角色:資料庫、開發人員、spring(等第三方)
對于案例1:開發人員不用知道事務的存在,事務全部交給資料庫來管理,資料庫自己決定什麼時候送出或復原,是以資料庫是事務的管理者
對于案例2、3:事務的送出和復原操作完全交給開發人員,開發人員來決定事務什麼時候送出或復原,是以開發人員是事務的管理者
對于案例4:開發人員完全不用關心事務,事務的送出和復原操作全部交給spring來管理,是以spring是事務的管理者
上述的特點也不是全部合理,如下文提到的spring的transactiontemplate,雖然屬于程式設計式事務,但是它的确是把事務的送出和復原交給了spring來管理。總之不用過分糾結于劃分事務模型
程式設計式事務:即通過手動程式設計方式來實作事務操作,大部分情況,都是類似于上述案例2、3情況,開發人員來管理事務的送出和復原,但也可能是spring自己來管理事務,如spring的transactiontemplate。
在前一篇文章中了解到,使用jdbc操作事務,程式設計非常麻煩,老是需要寫一套模闆式的try catch代碼,是以我們可以将try catch代碼封裝成一個模闆,這就引出了spring的transactiontemplate:
案例如下:
transactiontemplate繼承了defaulttransactiondefinition,有了預設的事務定義,也可以自定義設定隔離級别、傳播屬性等
transactiontemplate需要一個platformtransactionmanager事務管理器,來執行事務的操作
transactiontemplate在transactioncallback中執行業務代碼,try catch的事務模闆代碼,則被封裝起來,包裹在業務代碼的周圍,詳細見transactiontemplate的execute方法,如下:
詳細過程如下:
第一步:根據事務定義擷取事務
第二步:執行業務代碼
在transactioncallback中的dointransaction中執行相應的業務代碼。
如果使用的是datasourcetransactionmanager,你就可以使用jdbctemplate來執行業務邏輯;或者直接使用connection,但是必須使用datasourceutils來擷取connection
如果使用的是hibernatetransactionmanager,就可以使用hibernatetemplate來執行業務邏輯,或者則可以使用sessionfactory的getcurrentsession方法來擷取目前線程綁定的session,不可使用sessionfactory的opensession方法。也是不可亂用的,下面詳細解釋
第三步:如果業務代碼出現異常,則復原事務,沒有異常則送出事務
復原與送出都是通過platformtransactionmanager事務管理器來進行的
我們可以看到,使用transactiontemplate,其實就做到了事務代碼和業務代碼的分離,分離之後的代價就是,必須保證他們使用的是同一類型事務。之後的聲明式事務實作分離也是同樣的原理,這裡就提前說明一下。
1 如果使用datasourcetransactionmanager:
1.2 業務代碼使用jdbctemplate.update(sql)來執行業務,這個方法是使用的connection從哪來的?是和上面的事務connection是同一個嗎?源碼如下:
jdbctemplate在執行sql時,會使用datasourceutils從datasource中擷取一個connection。擷取過程如下:
也是先擷取和目前線程綁定的connectionholder(由于事務在執行業務邏輯前已經開啟,已經有了和目前線程綁定的connectionholder),是以會擷取到和事務中使用的connectionholder,這樣就保證了他們使用的是同一個connection了,自然就可以正常送出和復原了
如果想使用connection,則需要使用datasourceutils從datasorce中擷取connection,不能直接從datasource中擷取connection。
2 如果使用hibernatetransactionmanager:
2.2業務代碼就不能使用jdbctemplate來執行相應的業務邏輯了,需要使用session來執行相應的操作,換成對應的hibernatetemplate來執行。
hibernatetemplate在執行save(user)的過程中,會擷取一個session,方式如下:
session = getsessionfactory().getcurrentsession();
即采用sessionfactory的自帶的getcurrentsession方法,擷取目前session。具體什麼政策呢?
hibernate定義了這樣的一個接口:currentsessioncontext,内容如下:
上述sessionfactory擷取目前session就是依靠currentsessioncontext的實作。它的實作如下:
在spring環境下,預設采用的是springsessioncontext,它擷取目前session的方式如下:
也是先擷取和目前線程綁定的sessionholder(由于事務在執行業務邏輯前已經開啟,已經有了和目前線程綁定的sessionholder),是以會擷取到和事務中使用的sessionholder,這樣就保證了他們使用的是同一個session了,自然就可以正常送出和復原了。
如果不想通過使用hibernatetemplate,想直接通過session來操作,同理則需要使用sessionfactory的getcurrentsession方法來擷取session,而不能使用sessionfactory的opensession方法。
以上分析都是基于hibernate4,hibenrate3的原理自行去看,都差不多。
spring可以有三種形式來配置事務攔截,不同配置形式僅僅是外在形式不同,裡面的攔截原理都是一樣的,是以先通過一個小例子了解利用aop實作事務攔截的原理
從上面的spring的transactiontemplate我們可以實作了,事務代碼和業務代碼的分離,但是分離的還不徹底,還是需要如下的代碼:
一大堆的設定,同時業務代碼必須嵌套在transactioncallback中,如何才能做到如下方式的徹底分離呢?
代碼分析如下:
首先需要一個原始的userdao,我們需要對它進行aop代理,産生代理對象proxyuserdao,之後儲存的功能就是使用proxyuserdao來執行
對userdao具體的代理過程如下:
使用代理工廠,設定要代理的對象,即target
proxyfactory.settarget(userdao);
對代理對象加入攔截器
分成2種情況,一種預設攔截原userdao的所有方法,一種是指定pointcut,即攔截原userdao的某些方法。
這裡使用proxyfactory.addadvice(transactioninterceptor);就表示預設攔截原userdao的所有方法。
如果使用proxyfactory.addadvisor(advisor),這裡的advisor可以簡單看成是pointcut和advice的組合,pointcut則是用于指定是否攔截某些方法。
上述addadvice就是使用了預設的pointcut,表示對所有方法都攔截,源碼如下:
defaultpointcutadvisor内容如下:
pointcut.true便表示攔截所有方法。
之後再詳細講解這個事務攔截器。
設定好代理工廠要代理的對象和攔截器後,便可以建立代理對象。
proxyuserdao=(userdao) proxyfactory.getproxy()
之後,我們在使用建立出的proxyuserdao時,就會首先進入攔截器,執行相關攔截器代碼,是以我們可以在這裡實作事務的處理
事務攔截器需要2個參數:
事務配置的提供者
用于指定哪些方法具有什麼樣的事務配置
可以通過屬性配置方式,或者通過其他一些配置方式,如下三種方式都是為了擷取事務配置提供者:
方式1:
<property name="transactionattributes"> <props> <prop key="*">propagation_required</prop> </props> </property>
方式2:
<tx:attributes>
</tx:attributes>
方式3:
@transactional(propagation=propagation.required)
事務管理器platformtransactionmanager
有了事務的配置,我們就可以通過事務管理器來擷取事務了。
在執行代理proxyuserdao的save(user)方法時,會先進入事務攔截器中,具體的攔截代碼如下:
第一步:首先擷取所執行方法的對應的事務配置
第二步:然後擷取指定的事務管理器platformtransactionmanager
第三步:根據事務配置,使用事務管理器建立出事務
第四步:繼續執行下一個攔截器,最終會執行到代理的原始對象的方法
第五步:一旦執行過程發生異常,使用事務攔截器進行事務的復原
第六步:如果沒有異常,則使用事務攔截器送出事務
一個事務攔截器的内容大緻就是這樣。
配置案例如下:
案例分析:
上面有三大配置:
事務管理器transactionmanager
事務配置的提供者transactionattributes(用于指定哪些方法具有什麼樣的事務配置)
有了以上2個元素,我們就可以建立出一個事務攔截器transactioninterceptor
要代理的對象target
transactionproxyfactorybean這個工廠bean建立代理對象的原理就是:通過proxyfactory來對target建立出代理對象了,同時加入上述事務攔截器,就可以實作事務攔截功能了
使用transactionproxyfactorybean的方式隻能針對一個target進行代理,如果想再代理一個target,就需要再配置一個transactionproxyfactorybean,比較麻煩,是以使用apo:config的配置形式,就可以針對符合pointcut的所有target都可以進行代理。
這裡有2種配置:
tx:advice:
有事務管理器transactionmanager和事務配置提供者attributes,就可以産生一個事務攔截器transactioninterceptor
aop:config:
這裡會對符合pointcut的bean建立出代理對象,同時加入上述建立的事務攔截器
使用aop:config可以在xml中進行代理的配置,有時候想在代碼中直接進行配置,這時候就需要使用注解@transactional。
xml中啟動@transactional注解掃描
在代碼中就可以通過配置@transactional來實作事務攔截了
在xml配置中啟動注解掃描,會把那些加入了@transactional标記的容器bean建立出代理對象,同時加入事務攔截器。在執行事務攔截的時候,會從@transactional注解中取出對應的事務配置和事務管理器配置,進而可以執行事務的建立等操作。
至此spring的事務體系大概就講完了,接下來就要說明如何實作分布式事務了,在涉及jotm、atomikos這些第三方架構之前,先要了解下,分布式事務的一些概念
1 x/open dtp模型、xa規範、2pc
2 jta、jts概念
3 jta接口定義的了解