1- 事務ACID
事務由一系列操作組成的,保證所有操作整體原子執行,完整的事務滿足ACID特性
原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事務完成(不管成功還是失?。?,系統必須確保它所建模的業務處于一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
隔離性(Isolation):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
持久性(Durability):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。
2- 事務管理器
Spring并不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
2.1- PlatformTransactionManager
org.springframework.transaction.PlatformTransactionManager是Spring事務管理器的接口,通過這個接口,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。
PlatformTransactionManager接口
提供了獲取TransactionDefinition的方法以及根據TransactionStatus相應的狀態進行提交或者回滾
TransactionStatus事務狀態接口
上面講到的調用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一個實現,這個接口的內容如下:
TransactionDefinition事務定義的接口
而TransactionDefinition接口內容如下:
屬性:
方法:
3- TransactionDefinition接口定義的事務的五個屬性
3.1- 傳播行為
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播,沒有默認值。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,并在自己的事務中運行
我們可以看 org.springframework.transaction.annotation.Propagation 枚舉類中定義了6個表示傳播行為的枚舉值:
- REQUIRED :如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
- SUPPORTS :如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- MANDATORY :如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- REQUIRES_NEW :創建一個新的事務,如果當前存在事務,則把當前事務掛起。
- NOT_SUPPORTED :以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- NEVER :以非事務方式運行,如果當前存在事務,則拋出異常。
- NESTED :如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于 REQUIRED 。
指定方法:通過使用 propagation 屬性設置,例如:
@Transactional(propagation = Propagation.REQUIRED)
3.2- 隔離級別
隔離級別是指若干個并發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:臟讀取、重復讀、幻讀。
并發事務引起的問題
在典型的應用程序中,多個事務并發運行,經常會操作相同的數據來完成各自的任務。并發雖然是必須的,但可能會導致一下的問題。
- 臟讀(Dirty reads)——臟讀發生在一個事務讀取了另一個事務改寫但尚未提交的數據時。如果改寫在稍后被回滾了,那么第一個事務獲取的數據就是無效的。
- 不可重復讀(Nonrepeatable read)——不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。這通常是因為另一個并發事務在兩次查詢期間進行了更新。
- 幻讀(Phantom read)——幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接著另一個并發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。
不可重復讀與幻讀的區別
- 不可重復讀的重點是修改:
同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了 ,在一個事務中前后兩次讀取的結果并不一致,導致了不可重復讀。 - 幻讀的重點在于新增或者刪除:
同樣的條件, 第1次和第2次讀出來的記錄數不一樣 - 從總的結果來看, 似乎不可重復讀和幻讀都表現為兩次讀取的結果不一致。但如果你從控制的角度來看, 兩者的區別就比較大:
對于前者, 只需要鎖住滿足條件的記錄。
對于后者, 要鎖住滿足條件及其相近的記錄。
我們可以看 org.springframework.transaction.annotation.Isolation 枚舉類中定義了五個表示隔離級別的值:
- DEFAULT :這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是: READ_COMMITTED ,Mysql為REPEATABLE_READ。
- READ_UNCOMMITTED :該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀和不可重復讀,因此很少使用該隔離級別。
- READ_COMMITTED :該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值。
- REPEATABLE_READ :該隔離級別表示一個事務在整個過程中可以多次重復執行某個查詢,并且每次返回的記錄都相同。即使在多次查詢之間有新增的數據滿足該查詢,這些新增的記錄也會被忽略。該級別可以防止臟讀和不可重復讀。
- SERIALIZABLE :所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
指定方法:通過使用 isolation 屬性設置,例如:
@Transactional(isolation = Isolation.DEFAULT)
3.3- 只讀
事務的第三個特性是它是否為只讀事務。如果事務只對后端的數據庫進行該操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設置為只讀,你就可以給數據庫一個機會,讓它應用它認為合適的優化措施。
3.4- 事務超時
為了使應用程序很好地運行,事務不能運行太長的時間。因為事務可能涉及對后端數據庫的鎖定,所以長時間的事務會不必要的占用數據庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。
3.5- 回滾規則
事務五邊形的最后一個方面是一組規則,這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常Unchecked時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)
但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。
4- Spring Boot 基于聲明式注解事務使用示例
在Spring Boot中,當我們使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依賴的時候,框 架會自動默認分別注入DataSourceTransactionManager或JpaTransactionManager。所以我們不需要任何額外 配置就可以用@Transactional注解進行事務的使用。關于事務管理器,不管是JPA還是JDBC等都實現自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依賴,框架會默認注入 DataSourceTransactionManager 實例。如果你添加的是 spring-boot-starter-data-jpa 依賴,框架會默認注入 JpaTransactionManager 實例。
4.1- 事務管理兩種方式
spring支持編程式事務管理和聲明式事務管理兩種方式。
編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務管理建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基于@Transactional注解的方式),便可以將事務規則應用到業務邏輯中。
4.2- 單事務管理開啟與配置
在啟動類中配置事務管理器
- 默認事務管理器,只需開啟事務管理器即可,不需顯示的配置
- 顯示的指明事務管理器
如果你項目做的比較大,添加的持久化依賴比較多,我們還是會選擇人為的指定使用哪個事務管理器
在Spring容器中,我們手工注解@Bean 將被優先加載,框架不會重新實例化其他的 PlatformTransactionManager 實現類。
4.3- 多事務管理器的配置
對于同一個工程中存在多個事務管理器進行配置
啟動類實現TransactionManagementConfigurer接口,通過@Bean注解注入兩個事務管理器,實現annotationDrivenTransactionManager接口方法,指定value缺省值情況下提供的默認事務管理器,如下:
注:
如果Spring容器中存在多個 PlatformTransactionManager 實例,并且沒有實現接口 TransactionManagementConfigurer 指定默認值,在我們在方法上使用注解 @Transactional 的時候,就必須要用value指定,如果不指定,則會拋出異常。
4.4- @Transaction 注解使用的注意事項
@Transactional屬性
noRollback示例
@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。
最佳實踐:
雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這只有在使用基于接口的代理時它才會生效。
@Transactional 注解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。
默認情況下,只有來自外部的方法調用才會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。
事務異常排查列表:
- 數據庫本身是否支持事務,如mysql的myisam引擎就不支持事務
- 多數據源,是否指定了正確的事務管理器
- 數據源配置是否正確(MyBatis的SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源是否一致)
- 私有方法不會生效事務(AOP默認不代理私有方法,也不會拋出異常)
- 被調用方法不會生效事務(this指針調用方法,并沒有使用AOP代理來執行方法)
- 不指明回滾的異常,方法內用戶拋出的checked異常不會生效回滾(默認只有unchecked異常發生回滾)
MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源一致即可,否則事務管理會不起作用。
@Transaction示例
方法上注解屬性會覆蓋類注解上的相同屬性
事務管理器保證事務方法內的SQL在同一個SqlSession中,相當于:
參考鏈接
Spring聲明式事務為何不回滾