事務定義
事務管理對於企業應用來說是至關重要的,主要作用是用來保證資料的一致性,比如轉賬問題。如下虛擬碼所示:
transaction begin
A賬戶扣100
B賬戶加100
transaction commit
要麼這兩部操作都完成,要麼都不做(原子性),否則資料就不完整。因為在一個事務中的操作可以看成是一個統一整體,所以事務可以定義為一個不可分割的工作單元。
一個事務可以以兩種方式結束:提交或者回滾。當事務提交時,所有對資料的修改都會儲存。如果事務中的某一部操作失敗了,則事務回滾,並且在事務中的所有對資料的改動全部取消。所以即便是事務失敗了,資料的完整性依然能夠保證(一致性)。
事務四大特性(ACID)
- 原子性(Atomicity):原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部成功,要麼全部失敗。
- 一致性(Consistency):事務在完成時,必須使所有的資料都保持一致狀態(事務前後狀態是一致的)。還是用上面的例子,A賬戶有1000元,B賬戶有500元,加起來是1500,A向B轉賬100元,事務結束後,A是900元,B是600元,加起來還是1500元,即使中間出問題了,事務回退,A和B的總餘額還是1500元,這就是一致性。
- 隔離性(Isolation):由併發事務所做的修改必須與任何其它併發事務所作的修改隔離。
- 永續性(Durability):事務完成後,對系統的影響是永久性的。
Spring事務管理
對事務的支援是Spring的一大特性,Spring對其事務管理提供了一致地抽象,具有如下優點:
- 為不同的事務管理API如JTA(Java Transaction API)、JDBC、Hibernate、JPA(Java Persistence API)、JDO(Java Data Objects)提供了一致地程式設計模型;
- 支援宣告式事務管理;
- 提供比JTA更簡單易用地程式設計式事務管理API;
- 和Spring資料訪問抽象的完美整合;
什麼是Spring提供的一致程式設計模型(consistent programming model)?
在Spring之前Java EE開發人員可以通過兩種方式來進行事務管理:全域性事務和區域性事務。全域性事務允許處理多個事務源,典型關聯式資料庫和訊息佇列。應用伺服器通過JTA(Java Transaction API)來管理全域性事務,而JTA又需要用到JNDI,通常JTA只能在應用伺服器環境下使用,因此全域性事務會限制程式碼的複用性。而區域性事務比如JDBC事務,雖然比較簡單,可以實現最基本的事務操作,但是隻能處理同一個資料來源的操作,如果涉及到多資料的操作或者分散式場景,JDBC事務就無能為力了。
Spring通過提供一個一致的程式設計模型來解決全域性事務和區域性事務的缺點,所謂的一致的程式設計模型其實可以理解為Spring對事務的抽象,即Spring只提供了事務管理器的介面:org.springframework.transaction.PlatformTransactionManager,通過這個介面,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,各個平臺自己負責實現具體的事務管理,Spring官方的說法叫事務策略(transaction strategy)。PlatformTransactionManager介面如下:
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
getTransaction(..)方法根據傳入的TransactionDefinition引數返回了一個TransactionStatus物件,TransactionStatus代表一個新的事務或者一個已存在的事務。TransactionDefinition是一個介面,主要設定事務隔離級別、傳播特性、超時間隔和是否只讀。
不管是使用宣告式還是程式設計式事務管理,都需要設定具體的事務管理器(PlatformTransactionManager介面的實現),在Spring中通過依賴注入的方式來指定,springboot會根據具體的依賴完成自動注入。
Spring提供了許多內建事務管理器實現,常用的有HibernateTransactionManager和DataSourceTransactionManager:
- DataSourceTransactionManager:資料來源事務管理器,提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS或MyBatis框架的事務管理;
- HibernateTransactionManager:提供對單個org.hibernate.SessionFactory事務支援,用於整合Hibernate框架時的事務管理;
- JtaTransactionManager:提供對分散式事務管理的支援,並將事務管理委託給Java EE應用伺服器事務管理器;
- JpaTransactionManager:提供對單個javax.persistence.EntityManagerFactory事務支援,用於整合JPA實現框架時的事務管理;
- JdoTransactionManager:提供對單個javax.jdo.PersistenceManagerFactory事務管理,用於整合JDO框架時的事務管理;
宣告式事務管理和程式設計式事務管理
spring支援程式設計式事務管理和宣告式事務管理兩種方式。
程式設計式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於程式設計式事務管理,spring推薦使用TransactionTemplate。
宣告式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。宣告式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中。
顯然宣告式事務管理要優於程式設計式事務管理,這正是spring倡導的非侵入式的開發方式。宣告式事務管理使業務程式碼不受汙染,一個普通的POJO物件,只要加上註解就可以獲得完全的事務支援。和程式設計式事務相比,宣告式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像程式設計式事務那樣可以作用到程式碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的程式碼塊獨立為方法等等。
宣告式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置檔案,另一種就是基於@Transactional註解。顯然基於註解的方式更簡單易用,更清爽。
Spring宣告式事務的配置
Spring Boot 使用事務非常簡單,首先在啟動類上新增@EnableTransactionManagement 註解,啟用註解事務管理,這等同於xml配置方式的 <tx:annotation-driven />。然後在訪問資料庫的Service方法上新增註解@Transactional即可。
在Spring Boot中,關於事務管理器,不管是JPA還是JDBC等都實現自介面 PlatformTransactionManager。當我們使用了spring-boot-starter-jdbc的時候,Spring Boot會自動注入DataSourceTransactionManager啟用幫助配置資料庫事務相關的類;如果你新增的是 spring-boot-starter-data-jpa 依賴,框架會預設注入 JpaTransactionManager 例項。
@Transactional 註解可以作用於介面、介面方法、類以及類方法上,作用於單個方法上時該方法將支援事務;作用在類上時,該類的所有 public 方法將都具有該型別的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。但是 Spring 建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到public方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會丟擲任何異常。
在@Transactional 後可以設定屬性,來設定事務的具體語義,有如下屬性:
value:型別是字串,可以設定具體的事務管理器,如@Transactional(value="txManager1")
大多數應用只需單個事務管理器,但是有些場合需要多個事務管理器,則需要人為的指定使用哪個事務管理器。可以在啟動類中新增如下方法,Debug測試,就能知道自動注入的是 PlatformTransactionManager 介面的哪個實現類。
@Bean public Object testBean(PlatformTransactionManager platformTransactionManager) { System.out.println("------>>" + platformTransactionManager.getClass().getName()); return new Object(); }
propagation:型別為列舉,用於設定事務的傳播行為,例如@Transactional(propagation = Propagation .REQUIRED )
- REQUIRED :如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。
- SUPPORTS :如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
- MANDATORY :如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
- REQUIRES_NEW :建立一個新的事務,如果當前存在事務,則把當前事務掛起。
- NOT_SUPPORTED :以非事務方式執行,如果當前存在事務,則把當前事務掛起。
- NEVER :以非事務方式執行,如果當前存在事務,則丟擲異常。
- NESTED :如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於 REQUIRED
isolation:型別為列舉,用於設定事務的隔離級別,例如:@Transactional(isolation = Isolation.DEFAULT)
- DEFAULT :這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,通常這值就是: READ_COMMITTED 。
- READ_UNCOMMITTED :該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料。該級別不能防止髒讀和不可重複讀,因此很少使用該隔離級別。
- READ_COMMITTED :該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。
- REPEATABLE_READ :該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止髒讀和不可重複讀。
- SERIALIZABLE :所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。
readOnly:型別為boolean,用於設定事務為只讀或讀寫
timeout:型別為int,用於設定事務的超時時間
rollbackFor:一系列類,用於設定出現某些異常(繼承自Throwable)時一定會觸發事務回退,如@Transactional(rollbackFor=Exception.class)
rollbackForClassName:一系列類名,用於設定出現某些異常(繼承自Throwable)時一定會觸發事務回退
noRollbackFor:一系列類,用於設定出現某些異常(繼承自Throwable)時一定不會觸發事務回退
noRollbackForClassName:一系列類名,用於設定出現某些異常(繼承自Throwable)時一定不會觸發事務回退
如果未設定事務屬性,則spring會為事務指定預設屬性,預設傳播行為:PROPAGATION_REQUIRED,預設隔離級別:ISOLATION_DEFAULT,事務為讀寫,預設RuntimeException會觸發回退,而檢查異常則不會觸發回退。
參考文獻: