天天看點

spring-事務管理

一個資料庫事務是一個被視為單一的工作單元的操作序列。這些操作應該要麼完整地執行,要麼完全不執行。事務管理是一個重要組成部分,RDBMS 面向企業應用程式,以確定資料完整性和一緻性。事務的概念可以描述為具有以下四個關鍵屬性說成是 ACID:

  • 原子性:事務應該當作一個單獨單元的操作,這意味着整個序列操作要麼是成功,要麼是失敗的。
  • 一緻性:這表示資料庫的引用完整性的一緻性,表中唯一的主鍵等。
  • 隔離性:可能同時處理很多有相同的資料集的事務,每個事務應該與其他事務隔離,以防止資料損壞。
  • 持久性:一個事務一旦完成全部操作後,這個事務的結果必須是永久性的,不能因系統故障而從資料庫中删除。

一個真正的 RDBMS 資料庫系統将為每個事務保證所有的四個屬性。使用 SQL 釋出到資料庫中的事務的簡單視圖如下:

  • 使用 begin transaction 指令開始事務。
  • 使用 SQL 查詢語句執行各種删除、更新或插入操作。
  • 如果所有的操作都成功,則執行送出操作,否則復原所有操作。

Spring 架構在不同的底層事務管理 APIs 的頂部提供了一個抽象層。Spring 的事務支援旨在通過添加事務能力到 POJOs 來提供給 EJB 事務一個選擇方案。Spring 支援程式設計式和聲明式事務管理。EJBs 需要一個應用程式伺服器,但 Spring 事務管理可以在不需要應用程式伺服器的情況下實作。

局部事物 vs. 全局事務

局部事務是特定于一個單一的事務資源,如一個 JDBC 連接配接,而全局事務可以跨多個事務資源事務,如在一個分布式系統中的事務。

局部事務管理在一個集中的計算環境中是有用的,該計算環境中應用程式元件和資源位于一個機關點,而事務管理隻涉及到一個運作在一個單一機器中的本地資料管理器。局部事務更容易實作。

全局事務管理需要在分布式計算環境中,所有的資源都分布在多個系統中。在這種情況下事務管理需要同時在局部和全局範圍内進行。分布式或全局事務跨多個系統執行,它的執行需要全局事務管理系統和所有相關系統的局部資料管理人員之間的協調。

程式設計式 vs. 聲明式

Spring 支援兩種類型的事務管理:

  • 程式設計式事務管理 :這意味着你在程式設計的幫助下有管理事務。這給了你極大的靈活性,但卻很難維護。
  • 聲明式事務管理 :這意味着你從業務代碼中分離事務管理。你僅僅使用注釋或 XML 配置來管理事務。

聲明式事務管理比程式設計式事務管理更可取,盡管它不如程式設計式事務管理靈活,但它允許你通過代碼控制事務。但作為一種橫切關注點,聲明式事務管理可以使用 AOP 方法進行子產品化。Spring 支援使用 Spring AOP 架構的聲明式事務管理。

Spring 事務抽象

Spring 事務抽象的關鍵是由 org.springframework.transaction.PlatformTransactionManager 接口定義,如下所示:

public interface PlatformTransactionManager {
   TransactionStatus getTransaction(TransactionDefinition definition);
   throws TransactionException;
   void commit(TransactionStatus status) throws TransactionException;
   void rollback(TransactionStatus status) throws TransactionException;
}      
序号 方法 & 描述
1

TransactionStatus getTransaction(TransactionDefinition definition)

根據指定的傳播行為,該方法傳回目前活動事務或建立一個新的事務。

2

void commit(TransactionStatus status)

該方法送出給定的事務和關于它的狀态。

3

void rollback(TransactionStatus status)

該方法執行一個給定事務的復原。

TransactionDefinition 是在 Spring 中事務支援的核心接口,它的定義如下:

public interface TransactionDefinition {
   int getPropagationBehavior();
   int getIsolationLevel();
   String getName();
   int getTimeout();
   boolean isReadOnly();
}      

int getPropagationBehavior()

該方法傳回傳播行為。Spring 提供了與 EJB CMT 類似的所有的事務傳播選項。

int getIsolationLevel()

該方法傳回該事務獨立于其他事務的工作的程度。

String getName()

該方法傳回該事務的名稱。

4

int getTimeout()

該方法傳回以秒為機關的時間間隔,事務必須在該時間間隔内完成。

5

boolean isReadOnly()

該方法傳回該事務是否是隻讀的。

Spring中的隔離級别

:

隔離 & 描述

TransactionDefinition.ISOLATION_DEFAULT

這是預設的隔離級别。

TransactionDefinition.ISOLATION_READ_COMMITTED

表明能夠阻止誤讀;可以發生不可重複讀和虛讀。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED

表明可以發生誤讀、不可重複讀和虛讀。

TransactionDefinition.ISOLATION_REPEATABLE_READ

表明能夠阻止誤讀和不可重複讀;可以發生虛讀。

TransactionDefinition.ISOLATION_SERIALIZABLE

表明能夠阻止誤讀、不可重複讀和虛讀。

Spring 事務的傳播屬性

傳播 & 描述

TransactionDefinition.PROPAGATION_MANDATORY

支援目前事務;如果不存在目前事務,則抛出一個異常。

TransactionDefinition.PROPAGATION_NESTED

如果存在目前事務,則在一個嵌套的事務中執行。

TransactionDefinition.PROPAGATION_NEVER

不支援目前事務;如果存在目前事務,則抛出一個異常。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED

不支援目前事務;而總是執行非事務性。

TransactionDefinition.PROPAGATION_REQUIRED

支援目前事務;如果不存在事務,則建立一個新的事務。

6

TransactionDefinition.PROPAGATION_REQUIRES_NEW

建立一個新事務,如果存在一個事務,則把目前事務挂起。

7

TransactionDefinition.PROPAGATION_SUPPORTS

支援目前事務;如果不存在,則執行非事務性。

8

TransactionDefinition.TIMEOUT_DEFAULT

使用預設逾時的底層事務系統,或者如果不支援逾時則沒有。

TransactionStatus 接口為事務代碼提供了一個簡單的方法來控制事務的執行和查詢事務狀态。

public interface TransactionStatus extends SavepointManager {
   boolean isNewTransaction();
   boolean hasSavepoint();
   void setRollbackOnly();
   boolean isRollbackOnly();
   boolean isCompleted();
}      

boolean hasSavepoint()

該方法傳回該事務内部是否有一個儲存點,也就是說,基于一個儲存點已經建立了嵌套事務。

boolean isCompleted()

該方法傳回該事務是否完成,也就是說,它是否已經送出或復原。

boolean isNewTransaction()

在目前事務時新的情況下,該方法傳回 true。

boolean isRollbackOnly()

該方法傳回該事務是否已标記為 rollback-only。

void setRollbackOnly()

該方法設定該事務為 rollback-only 标記。

資料庫隔離級别

隔離級别 隔離級别的值 導緻的問題
Read-Uncommitted 導緻髒讀
Read-Committed 避免髒讀,允許不可重複讀和幻讀
Repeatable-Read 避免髒讀,不可重複讀,允許幻讀
Serializable 串行化讀,事務隻能一個一個執行,避免了髒讀、不可重複讀、幻讀。執行效率慢,使用時慎重

髒讀:一事務對資料進行了增删改,但未送出,另一事務可以讀取到未送出的資料。如果第一個事務這時候復原了,那麼第二個事務就讀到了髒資料。

不可重複讀:一個事務中發生了兩次讀操作,第一次讀操作和第二次操作之間,另外一個事務對資料進行了修改,這時候兩次讀取的資料是不一緻的。

幻讀:第一個事務對一定範圍的資料進行批量修改,第二個事務在這個範圍增加一條資料,這時候第一個事務就會丢失對新增資料的修改。

總結:

隔離級别越高,越能保證資料的完整性和一緻性,但是對并發性能的影響也越大。

大多數的資料庫預設隔離級别為 Read Commited,比如 SqlServer、Oracle

少數資料庫預設隔離級别為:Repeatable Read 比如: MySQL InnoDB

事務的嵌套

通過上面的理論知識的鋪墊,我們大緻知道了資料庫事務和spring事務的一些屬性和特點,接下來我們通過分析一些嵌套事務的場景,來深入了解spring事務傳播的機制。

假設外層事務 Service A 的 Method A() 調用 内層Service B 的 Method B()

PROPAGATION_REQUIRED(spring 預設)

如果ServiceB.methodB() 的事務級别定義為 PROPAGATION_REQUIRED,那麼執行 ServiceA.methodA() 的時候spring已經起了事務,這時調用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經運作在 ServiceA.methodA() 的事務内部,就不再起新的事務。

假如 ServiceB.methodB() 運作的時候發現自己沒有在事務中,他就會為自己配置設定一個事務。

這樣,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出現異常,事務都會被復原。

PROPAGATION_REQUIRES_NEW

比如我們設計 ServiceA.methodA() 的事務級别為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務級别為 PROPAGATION_REQUIRES_NEW。

那麼當執行到 ServiceB.methodB() 的時候,ServiceA.methodA() 所在的事務就會挂起,ServiceB.methodB() 會起一個新的事務,等待 ServiceB.methodB() 的事務完成以後,它才繼續執行。

他與 PROPAGATION_REQUIRED 的事務差別在于事務的復原程度了。因為 ServiceB.methodB() 是新起一個事務,那麼就是存在兩個不同的事務。如果 ServiceB.methodB() 已經送出,那麼 ServiceA.methodA() 失敗復原,ServiceB.methodB() 是不會復原的。如果 ServiceB.methodB() 失敗復原,如果他抛出的異常被 ServiceA.methodA() 捕獲,ServiceA.methodA() 事務仍然可能送出(主要看B抛出的異常是不是A會復原的異常)。

PROPAGATION_SUPPORTS

假設ServiceB.methodB() 的事務級别為 PROPAGATION_SUPPORTS,那麼當執行到ServiceB.methodB()時,如果發現ServiceA.methodA()已經開啟了一個事務,則加入目前的事務,如果發現ServiceA.methodA()沒有開啟事務,則自己也不開啟事務。這種時候,内部方法的事務性完全依賴于最外層的事務。

PROPAGATION_NESTED

現在的情況就變得比較複雜了, ServiceB.methodB() 的事務屬性被配置為 PROPAGATION_NESTED, 此時兩者之間又将如何協作呢? 
ServiceB#methodB 如果 rollback, 那麼内部事務(即 ServiceB#methodB) 将復原到它執行前的 SavePoint 而外部事務(即 ServiceA#methodA) 可以有以下兩種處理方式:

a、捕獲異常,執行異常分支邏輯

void methodA() { 

        try { 

            ServiceB.methodB(); 

        } catch (SomeException) { 

            // 執行其他業務, 如 ServiceC.methodC(); 

        } 

    }      

這種方式也是嵌套事務最有價值的地方, 它起到了分支執行的效果, 如果 ServiceB.methodB 失敗, 那麼執行 ServiceC.methodC(), 而 ServiceB.methodB 已經復原到它執行之前的 SavePoint, 是以不會産生髒資料(相當于此方法從未執行過), 這種特性可以用在某些特殊的業務中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點。

b、 外部事務復原/送出 代碼不做任何修改, 那麼如果内部事務(ServiceB#methodB) rollback, 那麼首先 ServiceB.methodB 復原到它執行之前的 SavePoint(在任何情況下都會如此), 外部事務(即 ServiceA#methodA) 将根據具體的配置決定自己是 commit 還是 rollback

另外三種事務傳播屬性基本用不到,在此不做分析。

總結

對于項目中需要使用到事務的地方,我建議開發者還是使用spring的TransactionCallback接口來實作事務,不要盲目使用spring事務注解,如果一定要使用注解,那麼一定要對spring事務的傳播機制和隔離級别有個詳細的了解,否則很可能發生意想不到的效果。

熬夜不易,點選請老王喝杯烈酒!!!!!!!