Spring事務

Spring事務


四個屬性

原子性(Atomicity):事務是一個完整的操作。事務的各步操作是不可分的(原子的);要麼都執行,要麼都不執行
一致性(Consistency):當事務完成時,數據必須處於一致狀態
隔離性(Isolation):對數據進行修改的所有併發事務是彼此隔離的,這表明事務必須是獨立的,它不應以任何方式依賴於或影響其他事務
永久性(Durability):事務完成後,它對數據庫的修改被永久保持,事務日誌能夠保持事務的永久性

事務隔離級別

隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

PROPAGATION_REQUIRED 表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啓動一個新的事務
PROPAGATION_SUPPORTS 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會在這個事務中運行
PROPAGATION_MANDATORY 表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常
PROPAGATION_REQUIRED_NEW 表示當前方法必須運行在它自己的事務中。一個新的事務將被啓動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager
PROPAGATION_NOT_SUPPORTED 表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager
PROPAGATION_NEVER 表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常
PROPAGATION_NESTED 表示如果當前已經存在一個事務,那麼該方法將會在嵌套事務中運行。嵌套的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行爲與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行爲的支持是有所差異的。可以參考資源管理器的文檔來確認它們是否支持嵌套事務

  1. PROPAGATION_REQUIRED
    說明: 如果當前已經存在事務,那麼加入該事務,如果不存在事務,創建一個事務,這是默認的傳播屬性值
@Transactional
public void service(){
    serviceA();
    serviceB();
}

@Transactional
serviceA();
@Transactional
serviceB();

serviceA 和 serviceB 都聲明瞭事務,默認情況下,propagation=PROPAGATION_REQUIRED,整個service調用過程中,只存在一個共享的事務,當有任何異常發生的時候,所有操作回滾。

2、PROPAGATION_SUPPORTS

說明:如果當前已經存在事務,那麼加入該事務,否則創建一個所謂的空事務(可以認爲無事務執行)。

看一個小例子,代碼如下:

public void service(){
     serviceA();
     throw new RunTimeException();
}

@Transactional(propagation=Propagation.SUPPORTS)
serviceA();

serviceA執行時當前沒有事務,所以service中拋出的異常不會導致 serviceA回滾。

再看一個小例子,代碼如下:

public void service(){
     serviceA();
}

@Transactional(propagation=Propagation.SUPPORTS)
serviceA(){
    do sql 1
    1/0;
    do sql 2
}

由於serviceA運行時沒有事務,這時候,如果底層數據源defaultAutoCommit=true,那麼sql1是生效的,如果defaultAutoCommit=false,那麼sql1無效,如果service有@Transactional標籤,serviceA共用service的事務(不再依賴defaultAutoCommit),此時,serviceA全部被回滾。

3、 PROPAGATION_MANDATORY

說明:當前必須存在一個事務,否則拋出異常。

看一個小例子,代碼如下:

public void service(){
     serviceB();
     serviceA();
}
serviceB(){
    do sql
}
@Transactional(propagation=Propagation.MANDATORY)
serviceA(){
    do sql
}

這種情況執行 service會拋出異常,如果defaultAutoCommit=true,則serviceB是不會回滾的,defaultAutoCommit=false,則serviceB執行無效。

4、PROPAGATN_REQUIRES_NEW

說明:如果當前存在事務,先把當前事務相關內容封裝到一個實體,然後重新創建一個新事務,接受這個實體爲參數,用於事務的恢復。更直白的說法就是暫停當前事務(當前無事務則不需要),創建一個新事務。 針對這種情況,兩個事務沒有依賴關係,可以實現新事務回滾了,但外部事務繼續執行。

看一個小例子,代碼如下:

@Transactional
public void service(){
    serviceB();
    try{
        serviceA();
    }catch(Exception e){
    }
}
serviceB(){
    do sql
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
serviceA(){
    do sql 1
    1/0;
    do sql 2
}

當調用service接口時,由於serviceA使用的是REQUIRES_NEW,它會創建一個新的事務,但由於serviceA拋出了運行時異常,導致serviceA整個被回滾了,而在service方法中,捕獲了異常,所以serviceB是正常提交的。 注意,service中的try … catch 代碼是必須的,否則service也會拋出異常,導致serviceB也被回滾。

5、Propagation.NOT_SUPPORTED

說明:如果當前存在事務,掛起當前事務,然後新的方法在沒有事務的環境中執行,沒有spring事務的環境下,sql的提交完全依賴於 defaultAutoCommit屬性值 。

看一個小例子,代碼如下:

@Transactional
public void service(){
     serviceB();
     serviceA();
}
serviceB(){
    do sql
}
@Transactional(propagation=Propagation.NOT_SUPPORTED)
serviceA(){
    do sql 1
    1/0;
    do sql 2
}

當調用service方法的時候,執行到serviceA方法中的1/0代碼時,拋出了異常,由於serviceA處於無事務環境下,所以 sql1是否生效取決於defaultAutoCommit的值,當defaultAutoCommit=true時,sql1是生效的,但是service由於拋出了異常,所以serviceB會被回滾。

6、 PROPAGATION_NEVER

說明: 如果當前存在事務,則拋出異常,否則在無事務環境上執行代碼。

看一個小例子,代碼如下:

public void service(){
    serviceB();
    serviceA();
}
serviceB(){
    do sql
}
@Transactional(propagation=Propagation.NEVER)
serviceA(){
    do sql 1
    1/0;
    do sql 2
}

上面的示例調用service後,若defaultAutoCommit=true,則serviceB方法及serviceA中的sql1都會生效。

7、 PROPAGATION_NESTED

說明: 如果當前存在事務,則使用 SavePoint 技術把當前事務狀態進行保存,然後底層共用一個連接,當NESTED內部出錯的時候,自行回滾到 SavePoint這個狀態,只要外部捕獲到了異常,就可以繼續進行外部的事務提交,而不會受到內嵌業務的干擾,但是,如果外部事務拋出了異常,整個大事務都會回滾。

注意: spring配置事務管理器要主動指定 nestedTransactionAllowed=true,如下所示:

    <bean id="dataTransactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataDataSource" />
        <property name="nestedTransactionAllowed" value="true" />
    </bean>

看一個小例子,代碼如下:

@Transactional
public void service(){
    serviceA();
    try{
        serviceB();
    }catch(Exception e){
    }
}
serviceA(){
    do sql
}
@Transactional(propagation=Propagation.NESTED)
serviceB(){
    do sql1
    1/0;
    do sql2
}

serviceB是一個內嵌的業務,內部拋出了運行時異常,所以serviceB整個被回滾了,由於service捕獲了異常,所以serviceA是可以正常提交的。

再來看一個例子,代碼如下:

@Transactional
public void service(){
     serviceA();
     serviceB();
     1/0;
}
@Transactional(propagation=Propagation.NESTED)
serviceA(){
    do sql
}
serviceB(){
    do sql
}

由於service拋出了異常,所以會導致整個service方法被回滾。(這就是跟PROPAGATION_REQUIRES_NEW不一樣的地方了,NESTED方式下的內嵌業務會受到外部事務的異常而回滾。)


參考

spring事務-說說Propagation及其實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章