天天看點

Spring事務管理全面分析

spring 事務屬性分析

什麼是事物

  事務管理對于企業應用而言至關重要。它保證了使用者的每一次操作都是可靠的,即便出現了異常的通路情況,也不至于破壞背景資料的完整性。就像銀行的自助取款機,通常都能正常為客戶服務,但是也難免遇到操作過程中機器突然出故障的情況,此時,事務就必須確定出故障前對賬戶的操作不生效,就像使用者剛才完全沒有使用過取款機一樣,以保證使用者和銀行的利益都不受損失。

  簡單來說:事物指的是邏輯上的一組操作,這組操作要麼全部成功,要麼全部失敗。

在 spring 中,事務是通過 transactiondefinition 接口來定義的。該接口包含與事務屬性有關的方法。具體代碼所示:

transactiondefinition 接口源碼:

public interface transactiondefinition {

// 若目前線程不存在事務中, 則開啟一個事務, 若目前存在事務, 則加入其中

int propagation_required = 0;

// 若目前存在事務, 則加入到事務中, 若不存在, 則以非事務的方式運作

int propagation_supports = 1;

// 若有事務, 則用目前的事務, 若沒有, 則直接抛出異常

int propagation_mandatory = 2;

// 若目前存在事務, 則挂起事務, 若目前不存在事務, 則開啟一個新事務運作

int propagation_requires_new = 3;

// 不支援以事務的方式運作, 若目前存在事務, 則将目前的事務挂起

int propagation_not_supported = 4;

// 不支援事務, 若目前線程含有事務, 則直接抛出異常

int propagation_never = 5;

// 這個時在原來的事務中通過 savepoint 的方式 開啟一個局部事務

int propagation_nested = 6;

// 預設隔離級别

int isolation_default = -1;

// read_uncommitted 級别

int isolation_read_uncommitted = connection.transaction_read_uncommitted;

// read_committed 級别

int isolation_read_committed = connection.transaction_read_committed;

// repeatable_read 級别

int isolation_repeatable_read = connection.transaction_repeatable_read;

// serializable 級别

int isolation_serializable = connection.transaction_serializable;

// 預設逾時時間

int timeout_default = -1;

// 擷取傳播行為

int getpropagationbehavior();

// 擷取隔離級别

int getisolationlevel();

// 擷取逾時時間

int gettimeout();

// 事務是否是隻讀模式

boolean isreadonly();

// 傳回事務的名字

string getname();

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

也許你會奇怪,為什麼接口隻提供了擷取屬性的方法,而沒有提供相關設定屬性的方法。其實道理很簡單,事務屬性的設定完全是程式員控制的,是以程式員可以自定義任何設定屬性的方法,而且儲存屬性的字段也沒有任何要求。唯一的要求的是,spring 進行事務操作的時候,通過調用以上接口提供的方法必須能夠傳回事務相關的屬性取值。

事物的特性

原子性:事物是一個不可分割的工作機關,事物中的操作要麼都發生,要麼都不發生

一緻性:事物前後資料的完整性必須保持一緻

隔離性:指多個使用者并發通路資料庫時,一個使用者的事物不能被其他使用者的事物所幹擾,多個并發事物之間資料要互相隔離。

持久性:一個事物一旦被送出,它對資料庫中資料的改變就是永久性的,即使資料庫發生故障也不應該對其有任何影響。

事務隔離級别

隔離級别是指若幹個并發的事務之間的隔離程度。transactiondefinition 接口中定義了五個表示隔離級别的常量:

transactiondefinition.isolation_default:這是預設值,表示使用底層資料庫的預設隔離級别。對大部分資料庫而言,通常這值就是transactiondefinition.isolation_read_committed。

transactiondefinition.isolation_read_uncommitted:該隔離級别表示一個事務可以讀取另一個事務修改但還沒有送出的資料。該級别不能防止髒讀和不可重複讀,是以很少使用該隔離級别。

transactiondefinition.isolation_read_committed:該隔離級别表示一個事務隻能讀取另一個事務已經送出的資料。該級别可以防止髒讀,這也是大多數情況下的推薦值。

transactiondefinition.isolation_repeatable_read:該隔離級别表示一個事務在整個過程中可以多次重複執行某個查詢,并且每次傳回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級别可以防止髒讀和不可重複讀。

transactiondefinition.isolation_serializable:所有的事務依次逐個執行,這樣事務之間就完全不可能産生幹擾,也就是說,該級别可以防止髒讀、不可重複讀以及幻讀。但是這将嚴重影響程式的性能。通常情況下也不會用到該級别。

至于事務隔離級别,小編在這裡不做介紹了,想了解的朋友請看mysql事務隔離級别

事務傳播行為

所謂事務的傳播行為是指,如果在開始目前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為。在transactiondefinition定義中包括了如下幾個表示傳播行為的常量:

transactiondefinition.propagation_required:如果目前存在事務,則加入該事務;如果目前沒有事務,則建立一個新的事務。

transactiondefinition.propagation_requires_new:建立一個新的事務,如果目前存在事務,則把目前事務挂起。

transactiondefinition.propagation_supports:如果目前存在事務,則加入該事務;如果目前沒有事務,則以非事務的方式繼續運作。

transactiondefinition.propagation_not_supported:以非事務方式運作,如果目前存在事務,則把目前事務挂起。

transactiondefinition.propagation_never:以非事務方式運作,如果目前存在事務,則抛出異常。

transactiondefinition.propagation_mandatory:如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。

transactiondefinition.propagation_nested:如果目前存在事務,則建立一個事務作為目前事務的嵌套事務來運作;如果目前沒有事務,則該取值等價于transactiondefinition.propagation_required。

  這裡需要指出的是,前面的六種事務傳播行為是 spring 從 ejb 中引入的,他們共享相同的概念。而 propagation_nested是 spring 所特有的。以 propagation_nested 啟動的事務内嵌于外部事務中(如果存在外部事務的話),此時,内嵌事務并不是一個獨立的事務,它依賴于外部事務的存在,隻有通過外部的事務送出,才能引起内部事務的送出,嵌套的子事務不能單獨送出。如果熟悉 jdbc 中的儲存點(savepoint)的概念,那嵌套事務就很容易了解了,其實嵌套的子事務就是儲存點的一個應用,一個事務中可以包括多個儲存點,每一個嵌套子事務。另外,外部事務的復原也會導緻嵌套子事務的復原。

事務逾時

  所謂事務逾時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動復原事務。在 transactiondefinition 中以 int 的值來表示逾時時間,其機關是秒。

事務的隻讀屬性

  事務的隻讀屬性是指,對事務性資源進行隻讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如資料源、 jms 資源,以及自定義的事務性資源等等。如果确定隻對事務性資源進行隻讀操作,那麼我們可以将事務标志為隻讀的,以提高事務處理的性能。在 transactiondefinition 中以 boolean 類型來表示該事務是否隻讀。

事務的復原規則

  通常情況下,如果在事務中抛出了未檢查異常(繼承自 runtimeexception 的異常),則預設将復原事務。如果沒有抛出任何異常,或者抛出了已檢查異常,則仍然送出事務。這通常也是大多數開發者希望的處理方式,也是 ejb 中的預設處理方式。但是,我們可以根據需要人為控制事務在抛出某些未檢查異常時任然送出事務,或者在抛出某些已檢查異常時復原事務。

spring 事務管理 api 分析

  spring事物管理高層抽象主要包括3個接口:

platformtransactionmanager(事務管理器)

transactiondefinition(事物定義資訊)

transactionstatus(事物具體運作狀态)

platformtransactionmanager: 這個接口中定義了 spring 執行事務的主方法:

public interface platformtransactionmanager {

// 開始事務

transactionstatus gettransaction(transactiondefinition definition) throws transactionexception;

// 送出事務

void commit(transactionstatus status) throws transactionexception;

// 復原事務

void rollback(transactionstatus status) throws transactionexception;

根據底層所使用的不同的持久化 api 或架構,platformtransactionmanager 的主要實作類大緻如下:

datasourcetransactionmanager:适用于使用jdbc和ibatis進行資料持久化操作的情況。

hibernatetransactionmanager:适用于使用hibernate進行資料持久化操作的情況。

jpatransactionmanager:适用于使用jpa進行資料持久化操作的情況。

另外還有jtatransactionmanager 、jdotransactionmanager、jmstransactionmanager等等。

  如果我們使用jta進行事務管理,我們可以通過 jndi 和 spring 的 jtatransactionmanager 來擷取一個容器管理的 datasource。jtatransactionmanager 不需要知道 datasource 和其他特定的資源,因為它将使用容器提供的全局事務管理。而對于其他事務管理器,比如datasourcetransactionmanager,在定義時需要提供底層的資料源作為其屬性,也就是 datasource。與 hibernatetransactionmanager 對應的是 sessionfactory,與 jpatransactionmanager 對應的是 entitymanagerfactory 等等。

transactiondefinition: 該接口在前面已經介紹過,它用于定義一個事務。它包含了事務的靜态屬性,比如:事務的隔離級别、事務傳播行為、逾時時間等等。

  spring 為我們提供了一個預設的實作類:defaulttransactiondefinition,該類适用于大多數情況。如果該類不能滿足需求,可以通過實作 transactiondefinition 接口來實作自己的事務定義。

transactionstatus

  platformtransactionmanager.gettransaction(…) 方法傳回一個 transactionstatus 對象。傳回的transactionstatus 對象可能代表一個新的或已經存在的事務(如果在目前調用堆棧有一個符合條件的事務)。transactionstatus 接口提供了一個簡單的控制事務執行和查詢事務狀态的方法。該接口代碼所示:

transactionstatus 接口中定義的主要方法:

public interface transactionstatus{

//傳回目前事務是否是新的;否則,将參與現有事務,或者可能根本不在實際事務中運作。

boolean isnewtransaction();

//隻設定事務復原。這訓示事務管理器,事務的唯一可能結果可能是復原,作為抛出異常的替代方法,而異常反過來又會觸發復原。

void setrollbackonly();

//傳回事務是否被标記為僅復原

boolean isrollbackonly();

程式設計式事務管理

  在 spring 出現以前,程式設計式事務管理對基于 pojo 的應用來說是唯一選擇。用過 hibernate 的人都知道,我們需要在代碼中顯式調用begintransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。

程式設計式和聲明式事務的差別

spring提供了對程式設計式事務和聲明式事務的支援,程式設計式事務允許使用者在代碼中精确定義事務的邊界,而聲明式事務(基于aop)有助于使用者将操作與事務規則進行解耦。

簡單地說:

程式設計式事務侵入到了業務代碼裡面,但是提供了更加詳細的事務管理

聲明式事務由于基于aop,是以既能起到事務管理的作用,又可以不影響業務代碼的具體實作。

如何實作程式設計式事務?

spring提供兩種方式的程式設計式事務管理,分别是:使用transactiontemplate和直接使用platformtransactionmanager。

使用transactiontemplate

采用transactiontemplate和采用其他spring模闆,如jdbctempalte和hibernatetemplate是一樣的方法。它使用回調方法,把應用程式從處理取得和釋放資源中解脫出來。如同其他模闆,transactiontemplate是線程安全的。代碼片段:

transactiontemplate tt = new transactiontemplate(); // 建立一個transactiontemplate

object result = tt.execute(

new transactioncallback(){

public object dotransaction(transactionstatus status){

updateoperation();

return resultofupdateoperation();

}); // 執行execute方法進行事務管理

transactioncallback 接口有一個子接口 transactioncallbackwithoutresult,該接口中定義了一個 dointransactionwithoutresult() 方法,transactioncallbackwithoutresult 接口主要用于事務過程中不需要傳回值的情況。當然,對于不需要傳回值的情況,我們仍然可以使用 transactioncallback 接口,并在方法中傳回任意值即可。

使用platformtransactionmanager

datasourcetransactionmanager datasourcetransactionmanager = new datasourcetransactionmanager(); //定義一個某個架構平台的transactionmanager,如jdbc、hibernate

datasourcetransactionmanager.setdatasource(this.getjdbctemplate().getdatasource()); // 設定資料源

defaulttransactiondefinition transdef = new defaulttransactiondefinition(); // 定義事務屬性

transdef.setpropagationbehavior(defaulttransactiondefinition.propagation_required); // 設定傳播行為屬性

transactionstatus status = datasourcetransactionmanager.gettransaction(transdef); // 獲得事務狀态

try {

// 資料庫操作

datasourcetransactionmanager.commit(status);// 送出

} catch (exception e) {

datasourcetransactionmanager.rollback(status);// 復原

聲明式事務管理

spring 的聲明式事務管理在底層是建立在 aop 的基礎之上的。其本質是對方法前後進行攔截,然後在目标方法開始之前建立或者加入一個事務,在執行完目标方法之後根據執行情況送出或者復原事務。

基于 transactioninterceptor 的聲明式事務管理

最初,spring 提供了 transactioninterceptor 類來實施聲明式事務管理功能。先看配置檔案:

基于 transactioninterceptor 的事務管理示例配置檔案

<bean id="transactioninterceptor"

class="org.springframework.transaction.interceptor.transactioninterceptor">

<property name="transactionmanager" ref="transactionmanager"/>

<property name="transactionattributes">

<props>

<prop key="transfer">propagation_required</prop>

</props>

</property>

</bean>

<bean id="bankservicetarget"

class="footmark.spring.core.tx.declare.origin.bankserviceimpl">

<property name="bankdao" ref="bankdao"/>

<bean id="bankservice"

class="org.springframework.aop.framework.proxyfactorybean">

<property name="target" ref="bankservicetarget"/>

<property name="interceptornames">

<list>

<idref bean="transactioninterceptor"/>

</list>

首先,我們配置了一個 transactioninterceptor 來定義相關的事務規則,他有兩個主要的屬性:一個是 transactionmanager,用來指定一個事務管理器,并将具體事務相關的操作委托給它;另一個是 properties 類型的 transactionattributes 屬性,它主要用來定義事務規則,該屬性的每一個鍵值對中,鍵指定的是方法名,方法名可以使用通配符,而值就表示相應方法的所應用的事務屬性。

指定事務屬性的取值有較複雜的規則,這在 spring 中算得上是一件讓人頭疼的事。具體的書寫規則如下:

傳播行為 [,隔離級别] [,隻讀屬性] [,逾時屬性] [不影響送出的異常] [,導緻復原的異常]

基于 transactionproxyfactorybean 的聲明式事務管理

前面的聲明式事務雖然好,但是卻存在一個非常惱人的問題:配置檔案太多。

為了緩解這個問題,spring 為我們提供了 transactionproxyfactorybean,用于将transactioninterceptor 和 proxyfactorybean 的配置合二為一:

class="footmark.spring.core.tx.declare.classic.bankserviceimpl">

class="org.springframework.transaction.interceptor.transactionproxyfactorybean">

這樣子是減少了proxy的代碼,但是每個service還是需要一個配置。是以我們可以使用自動代理的配置,這樣子就減少了大量的配置。也應該是最常用的。

!-- spring事務管理 -->

<bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager">

<property name="datasource" ref="datasource" />

<!-- 配置事務的傳播特性 -->

<bean id="basetransactionproxy" class="org.springframework.transaction.interceptor.transactionproxyfactorybean" abstract="true" >

<property name="transactionmanager" ref="transactionmanager" />

<prop key="add*">propagation_required</prop>

<prop key="edit*">propagation_required</prop>

<prop key="remove*">propagation_required</prop>

<prop key="insert*">propagation_required</prop>

<prop key="update*">propagation_required</prop>

<prop key="del*">propagation_required</prop>

<prop key="*">readonly</prop>

如此一來,配置檔案與先前相比簡化了很多。我們把這種配置方式稱為 spring 經典的聲明式事務管理。相信在早期使用 spring 的開發人員對這種配置聲明式事務的方式一定非常熟悉。

但是,顯式為每一個業務類配置一個 transactionproxyfactorybean 的做法将使得代碼顯得過于刻闆,為此我們可以使用自動建立代理的方式來将其簡化,使用自動建立代理是純 aop 知識,請讀者參考相關文檔,不在此贅述。

基于 命名空間的聲明式事務管理

前面兩種聲明式事務配置方式奠定了 spring 聲明式事務管理的基石。在此基礎上,spring 2.x 引入了 命名空間,結合使用 命名空間,帶給開發人員配置聲明式事務的全新體驗,配置變得更加簡單和靈活。另外,得益于 命名空間的切點表達式支援,聲明式事務也變得更加強大。

基于 的事務管理示例配置檔案:

class="footmark.spring.core.tx.declare.namespace.bankserviceimpl">

<tx:advice id="bankadvice" transaction-manager="transactionmanager">

<tx:attributes>

<tx:method name="transfer" propagation="required"/>

</tx:attributes>

</tx:advice>

<aop:config>

<aop:pointcut id="bankpointcut" expression="execution(* *.transfer(..))"/>

<aop:advisor advice-ref="bankadvice" pointcut-ref="bankpointcut"/>

</aop:config>

基于 @transactional 的聲明式事務管理

除了基于命名空間的事務配置方式,spring 2.x 還引入了基于 annotation 的方式,具體主要涉及@transactional 标注。@transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。

@transactional(propagation = propagation.required)

public boolean transfer(long fromid, long toid, double amount) {

return bankdao.transfer(fromid, toid, amount);

但是使用這種我們就必須啟用tx的annotation:

<tx:annotation-driven transaction-manager="transactionmanager"/>

總結:

  程式設計式事務:所謂程式設計式事務指的是通過編碼方式實作事務,即類似于jdbc程式設計實作事務管理。管理使用transactiontemplate或者直接使用底層的platformtransactionmanager。對于程式設計式事務管理,spring推薦使用transactiontemplate。

  聲明式事務:管理建立在aop之上的。其本質是對方法前後進行攔截,然後在目标方法開始之前建立或者加入一個事務,在執行完目标方法之後根據執行情況送出或者復原事務。聲明式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,隻需在配置檔案中做相關的事務規則聲明(或通過基于@transactional注解的方式),便可以将事務規則應用到業務邏輯中。

顯然聲明式事務管理要優于程式設計式事務管理,這正是spring倡導的非侵入式的開發方式。

聲明式事務管理使業務代碼不受污染,一個普通的pojo對象,隻要加上注解就可以獲得完全的事務支援。和程式設計式事務相比,聲明式事務唯一不足地方是,後者的最細粒度隻能作用到方法級别,無法做到像程式設計式事務那樣可以作用到代碼塊級别。但是即便有這樣的需求,也存在很多變通的方法,比如,可以将需要進行事務管理的代碼塊獨立為方法等等。

---------------------