天天看點

Spring事務管理詳解

Spring事務介紹

Spring并不直接管理事務,而是提供了多種事務管理器,他們将事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平台架構的事務來實作。

Spring事務管理器的接口是

org.springframework.transaction.PlatformTransactionManager

,通過這個接口,Spring為各個ORM平台如JDBC、Hibernate、MyBatis等都提供了對應的事務管理器,但是具體的實作就是各個平台自己的事情了。

從這裡可知具體的具體的事務管理機制對Spring來說是透明的,它并不關心那些,那些是對應各個平台需要關心的,是以Spring事務管理的一個優點就是為不同的事務API提供一緻的程式設計模型,如JTA、JDBC、Hibernate、JPA。下面分别介紹各個平台架構實作事務管理的機制。

相關接口介紹:

spring主要通過以下三個接口對事務進行管理:

1、PlatformTransactionManager 平台事務管理器

public interface PlatformTransactionManager {
    // 傳回一個已經激活的事務或建立一個新的事務(根據給定的TransactionDefinition類型參數定義的事務屬性),
    // 傳回的是TransactionStatus對象代表了目前事務的狀态,其中該方法抛出TransactionException(未檢查異常)
    // 表示事務由于某種原因失敗。
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    // 送出給定的事務,檢查其狀态。如果事務被标記為rollback-only,則執行復原。
    // 如果事務不是新的事務,則忽略送出周圍适當的事務。如果先前的事務被挂起,則在送出新事務後恢複先前的事務。
    void commit(TransactionStatus status) throws TransactionException;

    // 執行給定事務的復原。如果事務不是一個新的事務,将其周邊适當的事務标記為rollback-only。
    // 如果先前的事務被挂起,則在復原新事務後恢複先前的事務。
    void rollback(TransactionStatus status) throws TransactionException;
}
           

它的具體實作類:

DataSourceTransactionManager(Spring JDBC或iBatis持久化資料時)

HibernateTransactionManager(Hibernate3.0持久化資料時)

JdoTransactionManager(持久化機制為Jdo時)

JpaTransactionManager (JPA持久化時)

........

Spring為不同的ORM持久化架構提供了不同PlatformTransactionManager接口實作,具體實作則是由不同的ORM架構完成。

2、TransactionDefinition 事務定義資訊(隔離、傳播、逾時、隻讀)

public interface TransactionDefinition {
    // 傳回事務傳播行為
    int getPropagationBehavior();

    // 傳回事務隔離級别
    int getIsolationLevel();

    // 傳回事務逾時時間
    int getTimeout();

    // 傳回事務是否隻讀
    boolean isReadOnly();

    // 傳回事務的名稱
    String getName();
}
           

3、TransactionStatus 事務具體運作狀态

public interface TransactionStatus extends SavepointManager {
    // 傳回目前事務是否是新事物
    boolean isNewTransaction();

    // 傳回目前事務是否有儲存點,即基于儲存點建立的嵌套事務
    boolean hasSavepoint();

    // 标記事務為rollback-only。這将訓示事務管理器,事務的唯一可能結果是復原,
    // 作為抛出異常的替代方法,該異常将反過來觸發復原。
    void setRollbackOnly();

    // 傳回事務是否是被标記為rollback-only
    boolean isRollbackOnly();

    // 如果适用,将底層會話重新整理到資料存儲區:例如,所有受影響的Hibernate/JPA會話。
    void flush();
    
    // 傳回事務是否完成(送出或復原)
    boolean isCompleted();
}
           

事務屬性

在定義事務之前,需要了解一些事務的參數,正如前邊TransactionDefinition類定義的,包括隔離級别、傳播機制、事務逾時、是否隻讀等,還包括復原規則定義等參數。

隔離級别

隔離級别定義一個事務可能接受其他并發事務活動受影響的程度。也可以想象成事務對于資料處理的自私程度。它是多個事務并發執行時,産生的。

多個事務同時運作,經常會為了完成他們的工作而操作同一個資料。并發雖然是必需的,但是會導緻以下問題:

  • 脹讀——A事務讀取資料并修改,未送出之間B事務又讀取了資料
  • 不可重複讀——A事務讀取資料,B事務讀取資料并修改,A事務再讀取資料時發現兩次資料不一緻
  • 幻讀——事務A讀取或删除了全部資料,事務B又insert一條資料,這時事務A發現莫名其妙的多了一條資料

理想狀态下,事務之間是完全隔離的。但是完全隔離會影響性能,因為隔離的實作依賴于資料庫中的鎖,侵占性鎖會阻礙并發,要求事務互相等待。

考慮到完全隔離會影響性能,而且并不是所有的情況都要求完全隔離,是以有時候可以在事務隔離方面靈活處理。是以,就有好幾個隔離級别。

Spring事務管理的隔離級别

解決事務的安全性問題:髒讀、不可重複讀、幻讀。下面是Spring事務的隔離級别:

含義
DEFAULT 使用資料庫預設的隔離級别(spring中的選擇項)
READ_UNCOMMITTED 允許你讀取還未送出的改變了的資料。可能導緻髒、幻、不可重複讀
READ_COMMITTED 允許在并發事務已經送出後讀取。可防止髒讀, 但幻讀、不可重複讀仍可能發生(Oracle預設的隔離級别)
REPEATABLE_READ 對相同字段的多次讀取是一緻的,除資料對事務本身改變。可防止髒、不可重複讀,但幻讀仍可能發生 (MySQL預設的隔離級别)
SERIALIZABLE 完全服從ACID的隔離級别,確定不發生髒、幻、不可重複讀。這在所有的隔離級别中是最慢的,它是典型的通過完全鎖定在事務中涉及的資料表來完成的。

事務傳播行為

當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運作,也可能開啟一個新事務,并在自己的事務中運作。在TransactionDefinition定義中包括了如下7個表示傳播行為的常量:

傳播行為 說明
PROPAGATION_REQUIRED 業務方法需要在一個事務中運作。如果方法運作時,已經處在一個事務中,那麼加入到該事務,否則為自己建立一個新的事務
PROPAGATION_NOT_SUPPORTED 聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會為它開啟事務。如果方法在一個事務中被調用,該事務會被挂起,在方法調用結束後,原先的事務便會恢複執行
PROPAGATION_REQUIRES_NEW 屬性表明不管是否存在事務,業務方法總會為自己發起一個新的事務。如果方法已經運作在一個事務中,則原有事務會被挂起,新的事務會被建立,直到方法執行結束,新事務才算結束,原先的事務才會恢複執行
PROPAGATION_MANDATORY 該屬性指定業務方法隻能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果業務方法在沒有事務的環境下調用,容器就會抛出異常。
PROPAGATION_SUPPORTS 這一事務屬性表明,方法可以受事務控制,也可以不。如果業務方法在某個事務範圍内被調用,則方法成為該事務的一部分。如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行
PROPAGATION_NEVER 指定業務方法絕對不能在事務範圍内執行。如果業務方法在某個事務中執行,容器會抛出異常,隻有業務方法沒有關聯到任何事務,才能正常執行
PROPAGATION_NESTED 如果一個活動的事務存在,則運作在一個嵌套的事務中. 如果沒有活動事務, 則按REQUIRED屬性執行.它使用了一個單獨的事務, 這個事務擁有多個可以復原的儲存點。内部事務的復原不會對外部事務造成影響。它隻對DataSourceTransactionManager事務管理器起效
注意REQUIRES_NEW和NESTED兩者的差別;

PROPAGATION_REQUIRES_NEW

啟動一個新的, 不依賴于環境的 "内部" 事務. 這個事務将被完全 commited 或 rolled back 而不依賴于外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當内部事務開始執行時, 外部事務将被挂起, 内務事務結束時, 外部事務将繼續執行。

PROPAGATION_NESTED

開始一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它将取得一個 savepoint. 如果這個嵌套事務失敗, 我們将復原到此 savepoint. 潛套事務是外部事務的一部分, 隻有外部事務結束後它才會被送出。

對嵌套事務的了解?

嵌套:是子事務在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個復原點,叫save point,然後執行子事務,這個子事務也算是父事務的一部分,然後子事務執行結束,父事務繼續執行。下面看幾個問題就懂了

如果子事務復原,會發生什麼?

答:父事務會復原到save point,然後嘗試其他的事務或者其他的業務邏輯,父事務之前的操作不會受到影響,更不會自動復原

如果父事務復原,會發生什麼?

答:父事務復原,子事務也會跟着復原。父事務結束之前,子事務不會送出。

嵌套事務的送出是什麼情況?

答:子事務是父事務的一部分,是以子事務先送出,父事務再送出

事務逾時

假設事務的運作時間變得格外的長,由于事務可能涉及對後端資料庫的鎖定,是以長時間運作的事務會不必要地占用資料庫資源,這時就可以聲明一個事務在特定時間後自動復原。

由于逾時時鐘在一個事務啟動的時候開始的,是以,有對于那些具有可能啟動一個新事物的傳播行為(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED)的方法來說,聲明事務逾時才有意義,預設為30S。

是否隻讀

如果事務隻對後端資料進行讀操作,那麼如果将事務設定為隻讀事務,可以利用後端資料庫優化措施進行适當優化。

隻讀事務”并不是一個強制選項,它隻是一個“暗示”,提示資料庫驅動程式和資料庫系統,這個事務并不包含更改資料的操作,那麼JDBC驅動程式和資料庫就有可能根據這種情況對該事務進行一些特定的優化,比方說不安排相應的資料庫鎖,以減輕事務對資料庫的壓力,畢竟事務也是要消耗資料庫的資源的。但是你非要在“隻讀事務”裡面修改資料,也并非不可以,隻不過對于資料一緻性的保護不像“讀寫事務”那樣保險而已。

是以,“隻讀事務”僅僅是一個性能優化的推薦配置而已,并非強制你要這樣做不可。

隻讀事務實在開啟事務時有資料庫實施的,是以隻對具備啟動新事務的傳播機制有效,如REQUIRED、REQUIRES_NEW、NESTED。

復原規則

復原規則定義了哪些異常引起復原,哪些不引起。在預設情況下,事務隻出現運作時異常(Runtime Exception)時復原,而在出現受檢查異常(Checked Exception)時不復原。

但是我們可以在Spring中進行定義來改變其預設行為。Spring在xml檔案配置事務時提供了rollback-for和no-rollback-for參數,來指定復原和不會滾的異常名稱,該名稱對應的類為Throwable的子類。

Spring的事務支援

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

簡單地說,程式設計式事務侵入到了業務代碼裡面,但是提供了更加詳細的事務管理,實作細粒度的事務控制;而聲明式事務由于基于AOP,是以既能起到事務管理的作用,又可以不影響業務代碼的具體實作。

如果你并不需要細粒度的事務控制,你可以使用聲明式事務,在Spring中,你隻需要在Spring配置檔案中做一些配置,即可将操作納入到事務管理中, 解除了和代碼的耦合, 這是 對應用代碼影響最小的選擇,從這一點再次驗證了Spring關于 AOP的概念。當你不需要事務管理的時候,可以直接從Spring配置檔案中移除該設定。

程式設計式事務

Spring提供了TransactionTemplate工具類,可以友善地讓開發人員手動的程式設計實作事務控制。程式設計式事務雖然可以精确控制事務,但是事務控制代碼必須侵入業務邏輯代碼中,耦合度高,後期難以維護。一般而言,不需要精确控制事務,是以采用的更多的是Spring的聲明式事務。

聲明式事務

Spring聲明式事務基于AOP實作,有兩種事務定義方式:xml配置和注解定義,前者使用tx命名空間,後者使用@Transactional注解。

由于聲明式事務是基于AOP實作的,是以隻能實作對方法的粗粒度的控制,而無法做到代碼塊級别的細粒度控制。

使用聲明式事務還是程式設計式事務管理,在很大程度上是 細粒度和易用性之間權衡。