參考:
https://www.jianshu.com/p/d23f22b5aed5
https://www.cnblogs.com/wangfg/p/9475788.html
https://www.cnblogs.com/alice-cj/p/10417097.html
https://blog.csdn.net/jiangyu1013/article/details/84397366
數據庫事務和@Transaction注解
數據庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要么完全地執行,要么完全地不執行。 事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合為一個要么全部成功要么全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。
事務的基本要素(ACID)
- 原子性(Atomicity):事務開始后所有操作,要么全部做完,要么全部不做,不可能停滯在中間環節。事務執行過程中出錯,會回滾到事務開始前的狀態,所有的操作就像沒有發生一樣。也就是說事務是一個不可分割的整體,就像化學中學過的原子,是物質構成的基本單位。
- 一致性(Consistency):事務開始前和結束后,數據庫的完整性約束沒有被破壞 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
- 隔離性(Isolation):同一時間,只允許一個事務請求同一數據,不同的事務之間彼此沒有任何干擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。
- 持久性(Durability):事務完成后,事務對數據庫的所有更新將被保存到數據庫,不能回滾。
當單進程執行時,很容易保證事務的4中基本要素,並且不會引發問題,然而當多個並發進程共同執行事務時候,可能會引發不同的問題。
事務隔離級別
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 特點 |
---|---|---|---|---|
未提交讀(read-uncommitted) | √ | √ | √ | 優點在於並發能力高,適合那些對數據一致性沒有要求而追求高並發的場景,缺點是臟讀 |
讀寫提交(read-committed) | × | √ | √ | 出現不可重復讀 |
可重復讀(repeatable-read) | × | × | √ | mysql的默認事務隔離級別。會出現幻讀 |
串行化(serializable) | × | × | × | 並行性低,一般不用。通常消除幻讀使用數據庫鎖的方式 |
- 臟讀:事務A讀取了事務B更新的數據,然后B回滾操作,那么A讀取到的數據是臟數據
- 不可重復讀:事務 A 多次讀取同一數據,事務 B 在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
- 幻讀:幻讀是針對多條數據庫記錄的。例如,系統管理員A將數據庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束后發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。
數據庫鎖
樂觀鎖
相對悲觀鎖而言,樂觀鎖假設認為數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測,如果發現沖突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。一般的實現樂觀鎖的方式就是記錄數據版本,並且不會造成線程的阻塞。
但是,版本的沖突會造成請求失敗的概率劇增,這時往往需要通過重入的機制將請求失敗的概率降低。而多次的重入又會帶來過多執行SQL的問題,為了克服這個問題,可以考慮使用按時間戳或者限制重入次數的辦法。
悲觀鎖
正如其名,它指的是對數據被外界修改持保守態度。因此在整個數據處理過程中,將數據處於鎖定狀態。 悲觀鎖的實現,往往依靠數據庫提供的鎖機制 (也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
數據庫兩種鎖可以實現悲觀鎖,排他鎖(Exclusive Lock,也叫X鎖)和共享鎖(Shared Lock,也叫S鎖)。排他鎖表示對數據進行寫操作,如果一個事務對對象加了排他鎖,其他事務就不能再給它加任何鎖了;共享鎖會為操作對象加共享鎖,可以允許其他共享鎖的加鎖操作,但是不允許排它鎖加鎖操作。
鎖粒度
MySQL鎖的粒度幾種,從小到大分別是,行鎖(Record Lock)、間隙鎖(Gap Lock)、Next-Key Lock、表鎖。
1、 行鎖:鎖定一行記錄
2、 間隙鎖:鎖定一個范圍,但是不包含記錄本身
3、Next-Key Lock:Record Lock和Gap Lock的組合,鎖定一個區間以及對應的記錄
4、 表鎖:鎖定整張表
MySQL innoDB加鎖是通過索引項判斷加鎖的范圍,即如果操作的對象上沒有索引或者沒有使用索引,則會導致鎖的粒度擴大。因為InnoDB索引采用的BTree結構,所以,如果不是唯一索引,則如果使用查詢值索引不存在,或者使用的條件查詢則會引發Next-Key Lock。
意向鎖
有了共享鎖(S鎖)和排它鎖(X鎖),能夠實現數據庫操作不同粒度的加鎖,但是在一個操作需要給整個表加X鎖的時候,需要確定整個表以及表的每一行沒有被加S鎖或者X鎖,那么如何確定表中每一行沒有被加鎖,需要掃描整個表嗎?如果表中有海量記錄需要長時間等待,此時需要有意向鎖。
Spring的@Transaction注解
在Spring中,為了 “擦除”令人厭煩的 try...catch...finally..語句,減少代碼中數據庫連接開閉和事務回滾提交的代碼, Spring利用其 AOP 為我們提供了一個數據庫事務的約定流程,這樣開發的代碼可讀性就更高,也更好維護。

@Transactional源碼分析:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { // 通過 bean name 指定事務管理器 @AliasFor("transactionManager") String value() default ""; // 同 value 屬性 @AliasFor("value") String transactionManager() default ""; // 傳播行為 Propagation propagation() default Propagation.REQUIRED; // 隔離級別 Isolation isolation() default Isolation.DEFAULT; // 超時時間 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; // 是否只讀事務 boolean readOnly() default false; // 方法在發生指定異常時回滾,默認是所有異常都囚滾 Class<? extends Throwable>[] rollbackFor() default {}; // 方法在發生指定異常名稱時回滾,默認是所有異常都回滾 String[] rollbackForClassName() default {}; // 方法在發生指定異常時不回滾,默認是所有異常都回滾 Class<? extends Throwable>[] noRollbackFor() default {}; // 方法在發生指定異常名稱時不回滾,默認是所有異常都回滾 String[] noRollbackForClassName() default {}; }
事務管理器
事務的打開、回滾和提交是由事務管理器來完成的。在Spring中,事務管理器的頂層接口為PlatformTransactionManager,Spring也定義了一些其他的接口和類。
在Spring Boot中,當你依賴於mybatis-spring-boot-starter之后,它會自動創建一個DataSource TransactionManager對象作為事務管理器;如果依賴於spring-boot-starter-data-jpa,則它會自動創建JpaTransactionManager對象作為事務管理器,所以我們一般不需要自己創建事務管理器而直接使用它們即可。
傳播行為
傳播行為是方法之間調用事務采取的策略。在大部分的情況下,我們會認為數據庫事務要么全部成功,要么全部失敗。但當執行批量任務時,我們有時不希望因為極少數的任務不能完成而回滾所有的批量任務 。
在Spring中,當一個方法調用另外一個方法時,可以讓事務采取不同的策略工作,如新建事務或者掛起當前事務等,這便是事務的傳播行為。例如,批量任務我們稱之為當前方法,當它調用單個任務時,稱單個任務為子方法。當前方法調用子方法的時候,讓每一個子方法不在當前事務中執行,而是創建一個新的事務,我們就說當前方法調用子方法的傳播行為為新建事務。此外,還可能讓方法在無事務、獨立事務中執行,這些完全取決於業務需求。
源碼分析:
public enum Propagation { // 需要事務。它是默認傳播行為,如果當前存在事務,就沿用當前事務,否則新建一個事務運行子方法 REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), // 支持事務,如果當前存在事務,就沿用當前事務,如果不存在 ,則繼續采用無事務的方式運行子方法 SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), // 必須使用事務,如果當前沒有事務,則會拋出異常,如果存在當前事務 ,就沿用當前事務 MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), // 無論當前事務是否存在,都會創建新事務運行方法,這樣新事務就可以擁有新的鎖和隔離級別等特性,與當前事務相互獨立 REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), // 不支持事務,當前存在事務時,將掛起事務,運行方法 NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), // 不支持事務,如果當前方法存在事務,則拋出異常,否則繼續使用無事務機制運行 NEVER(TransactionDefinition.PROPAGATION_NEVER), // 在當前方法調用子方法時,如果子方法發生異常,只因滾子方法執行過的 SQL,而不回滾當前方法的事務 NESTED(TransactionDefinition.PROPAGATION_NESTED); private final int value; Propagation(int value) { this.value = value; } public int value() { return this.value; } }
常用的傳播行為是REQUIRED、REQUIRES_NEW、NESTED三種。
@Transactional自調用失效問題
Spring數據庫事務的實現原理是AOP,而AOP的原理是動態代理。在事務自調用的過程中,是類自身的調用,而不是代理對象去調用,那么就不會產生AOP,這樣Spring就不能把你的代碼織入到約定的流程中,於是就產生了@Transactional自調用失效的場景。
有兩種解決方法:用一個Service去調用另一個Service,這樣就是代理對象的調用,Spring就會將代碼織入事務流程;或者從Spring IoC容器中獲取代理對象去啟用AOP。
參考資料
- 《深入淺出Spring Boot 2.X》
- 《mysql數據庫事務與鎖》--堯亦
鏈接:https://www.jianshu.com/p/d23f22b5aed5
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
spring的@Transactional注解詳細用法
概述
事務管理對於企業應用來說是至關重要的,即使出現異常情況,它也可以保證數據的一致性。
Spring Framework對事務管理提供了一致的抽象,其特點如下:
- 為不同的事務API提供一致的編程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
- 支持聲明式事務管理,特別是基於注解的聲明式事務管理,簡單易用
- 提供比其他事務API如JTA更簡單的編程式事務管理API
- 與spring數據訪問抽象的完美集成
事務管理方式
spring支持編程式事務管理和聲明式事務管理兩種方式。
編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務管理建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於@Transactional注解的方式),便可以將事務規則應用到業務邏輯中。
顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上注解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立為方法等等。
聲明式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置文件,另一種就是基於@Transactional注解。顯然基於注解的方式更簡單易用,更清爽。
自動提交(AutoCommit)與連接關閉時的是否自動提交
自動提交
默認情況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果
執行失敗則隱式的回滾事務。
有些數據連接池提供了關閉事務自動提交的設置,最好在設置連接池時就將其關閉。但C3P0沒有提供這一特性,只能依靠spring來設置。
因為JDBC規范規定,當連接對象建立時應該處於自動提交模式,這是跨DBMS的缺省值,如果需要,必須顯式的關閉自動提交。C3P0遵守這一規范,讓客戶代碼來顯式的設置需要的提交模式。
連接關閉時的是否自動提交
當一個連接關閉時,如果有未提交的事務應該如何處理?JDBC規范沒有提及,C3P0默認的策略是回滾任何未提交的事務。這是一個正確的策略,但JDBC驅動提供商之間對此問題並沒有達成一致。
C3P0的autoCommitOnClose屬性默認是false,沒有十分必要不要動它。或者可以顯式的設置此屬性為false,這樣會更明確。
基於注解的聲明式事務管理配置
spring-servlet.xml
1 <!-- transaction support--> 2 <!-- PlatformTransactionMnager --> 3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 4 <property name="dataSource" ref="dataSource" /> 5 </bean> 6 <!-- enable transaction annotation support --> 7 <tx:annotation-driven transaction-manager="txManager" />
還要在spring-servlet.xml中添加tx名字空間
1 ... 2 xmlns:tx="http://www.springframework.org/schema/tx" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 ... 6 7 http://www.springframework.org/schema/tx 8 9 10 http://www.springframework.org/schema/tx/spring-tx.xsd 11 12 ...
MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源一致即可,否則事務管理會不起作用。
另外需要下載依賴包aopalliance.jar放置到WEB-INF/lib目錄下。否則spring初始化時會報異常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
spring事務特性
TransactionDefinition接口定義以下特性:
事務隔離級別
隔離級別是指若干個並發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀,不可重復讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重復執行某個查詢,並且每次返回的記錄都相同。該級別可以防止臟讀和不可重復讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
默認設置為底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那么就是none,沒有超時限制。
事務只讀屬性
只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。
默認為讀寫事務。
spring事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。
還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。
@Transactional注解
@Transactional屬性
屬性 | 類型 | 描述 |
---|---|---|
value | String | 可選的限定描述符,指定使用的事務管理器 |
propagation | enum: Propagation | 可選的事務傳播行為設置 |
isolation | enum: Isolation | 可選的事務隔離級別設置 |
readOnly | boolean | 讀寫或只讀事務,默認讀寫 |
timeout | int (in seconds granularity) | 事務超時時間設置 |
rollbackFor | Class對象數組,必須繼承自Throwable | 導致事務回滾的異常類數組 |
rollbackForClassName | 類名數組,必須繼承自Throwable | 導致事務回滾的異常類名字數組 |
noRollbackFor | Class對象數組,必須繼承自Throwable | 不會導致事務回滾的異常類數組 |
noRollbackForClassName | 類名數組,必須繼承自Throwable | 不會導致事務回滾的異常類名字數組 |
用法
1. 在需要事務管理的地方加@Transactional 注解。@Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。
2. @Transactional 注解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯, 但是這個被注解的方法將不會展示已配置的事務設置。
3. 注意僅僅 @Transactional 注解的出現不足於開啟事務行為,它僅僅 是一種元數據。必須在配置文件中使用配置元素,才真正開啟了事務行為。
4. 通過 元素的 "proxy-target-class" 屬性值來控制是基於接口的還是基於類的代理被創建。如果 "proxy-target-class" 屬值被設置為 "true",那么基於類的代理將起作用(這時需要CGLIB庫cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 屬值被設置為 "false" 或者這個屬性被省略,那么標准的JDK基於接口的代理將起作用。
標准的JDK基於接口的代理將起作用-->
proxy-target-class="false"/>
基於類的代理將起作用 ,同時 cglib.jar必須在CLASSPATH中
proxy-target-class="true"/>
-->
非JTA事務(即非分布式事務), 事務配置的時候 ,需要指定dataSource屬性(非分布式事務,事務是在數據庫創建的鏈接上開啟。)-->
JTA事務(非分布式事務), 事務配置的時候 ,不能指定dataSource屬性(分布式事務,是有全局事務來管理數據庫鏈接的)-->
注解@Transactional cglib與java動態代理最大區別是代理目標對象不用實現接口,那么注解要是寫到接口方法上,要是使用cglib代理,這是注解事物就失效了,為了保持兼容注解最好都寫到實現類方法上。
5. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional 注解,只能當你設置了基於接口的代理時它才生效。因為注解是 不能繼承 的,這就意味着如果正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝。
6. @Transactional 的事務開啟 ,或者是基於接口的 或者是基於類的代理被創建。所以在同一個類中一個方法調用另一個方法有事務的方法,事務是不會起作用的。
public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) ;
//刪除指定id的person,flag
public void delete(Integer personid,boolean flag) ;
}
public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
public void delete(Integer personid){
try{
this.delete(personid,true)
System.out.println("delete success");
}catch(Exception e){
System.out.println("delete failed");
}
}
@Transactional
//此時,事務根本就沒有開啟, 即數據庫會默認提交該操作,即記錄別刪除掉 public void delete(Integer personid,boolean flag){
if(flag == ture){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("運行期例外");
}
}
}
public class PersonServiceBeanTest{
PersonService ps = new PersonServiceBean ();
ps.delete(5);
}
7. Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。
-----------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) ;
//獲取person
public Person getPerson(Integer personid);
}
//PersonServiceBean 實現了PersonService 接口,則基於接口的還是基於類的代理 都可以實現事務
@Transactional public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
//發生了unchecked異常,事務回滾, @Transactional
public void delete(Integer personid){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("運行期例外");
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) throws Exception;
//獲取person
public Person getPerson(Integer personid);
}
@Transactional
public class PersonServiceBean implements PersonService {
//發生了checked異常,事務不回滾,即數據庫記錄仍能被刪除,
//checked的例外,需要我們在外部用try/catch語法對調用該方法的地方進行包含 @Transactional
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("運行期例外");
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------
但是,對於checked這種例外,默認情況下它是不會進行事務回滾的,但是如果我們需要它進行事務回滾,這時候可以在delete方法上通過@Transaction這個注解來修改它的行為。
@Transactional
public class PersonServiceBean implements PersonService {
@Transactional(rollbackFor=Exception.class)
//rollbackFor這屬性指定了,既使你出現了checked這種例外,那么它也會對事務進行回滾
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("運行期例外");
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------
在PersonServiceBean這個業務bean里面,有一些事務是不需要事務管理的,好比說獲取數據的getPersons方法,getPerson方法。因為@Transactional 放在了類的上面。
此時,可以采用propagation這個事務屬性@Transactional(propagation=Propagation.NOT_SUPPORTED),propagation這個屬性指定了事務傳播行為,我們可以指定它不支持事務,當我們這么寫了之后,Spring容器在getPersons方法執行前就不會開啟事務.
@Transactional
public class PersonServiceBean implements PersonService {
@Transactional(propagation=Propagation.NOT_SUPPORTED)
//則此方法 就不會開啟事務了
public Person getPerson(Integer personid)
{
}
}
1. Spring事務的基本原理
事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編碼式和聲明式的兩種方式。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional 注解的方式。注釋配置是目前流行的使用方式,因此本文將着重介紹基於@Transactional 注解的事務管理。
使用@Transactional的相比傳統的我們需要手動開啟事務,然后提交事務來說。它提供如下方便
- 根據你的配置,設置是否自動開啟事務
- 自動提交事務或者遇到異常自動回滾
聲明式事務(@Transactional)基本原理如下:
- 配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。
- spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且為這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。
- 真正的數據庫層的事務提交和回滾是通過binlog或者redo log實現的。
2. @Transactional基本配置解析
@Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("maskwang"); logger.info("save the user{}",user); userRepository.save(user); }
如上面這個例子一樣,很輕松的就能應用事務。只需要在方法上加入@Transactional注解。當@Transactional加在方法上,表示對該方法應用事務。當加在類上,表示對該類里面所有的方法都應用相同配置的事務。接下來對@Transactional的參數解析。
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
transactionManager()
表示應用哪個TransactionManager
.常用的有如下的事務管理器

isolation()
表示隔離級別

臟讀:一事務對數據進行了增刪改,但未提交,另一事務可以讀取到未提交的數據。如果第一個事務這時候回滾了,那么第二個事務就讀到了臟數據。
不可重復讀:一個事務中發生了兩次讀操作,第一次讀操作和第二次操作之間,另外一個事務對數據進行了修改,這時候兩次讀取的數據是不一致的。
幻讀:第一個事務對一定范圍的數據進行批量修改,第二個事務在這個范圍增加一條數據,這時候第一個事務就會丟失對新增數據的修改。
不可重復讀的重點是修改 :同樣的條件, 你讀取過的數據,再次讀取出來發現值不一樣了幻讀的重點在於新增或者刪除:同樣的條件, 第 1 次和第 2 次讀出來的記錄數不一樣
propagation()
表示事務的傳播屬性
事務的傳播是指事務的嵌套的時候,它們的事務屬性。常見的傳播屬性有如下幾個
-
PROPAGATION_REQUIRED
(spring 默認)
假設外層事務 Service A 的 Method A() 調用 內層Service B 的 Method B()。如果ServiceB.methodB() 的事務級別定義為 PROPAGATION_REQUIRED,那么執行 ServiceA.methodA() 的時候spring已經起了事務,這時調用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經運行在 ServiceA.methodA() 的事務內部,就不再起新的事務。假如 ServiceB.methodB() 運行的時候發現自己沒有在事務中,他就會為自己分配一個事務。不管如何,ServiceB.methodB()都會在事務中。 -
PROPAGATION_REQUIRES_NEW
比如我們設計 ServiceA.methodA() 的事務級別為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務級別為 PROPAGATION_REQUIRES_NEW。那么當執行到 ServiceB.methodB() 的時候,ServiceA.methodA() 所在的事務就會掛起,ServiceB.methodB() 會起一個新的事務,等待 ServiceB.methodB() 的事務完成以后,它才繼續執行。它與1中的區別在於ServiceB.methodB() 新起了一個事務。如過ServiceA.methodA() 發生異常,ServiceB.methodB() 已經提交的事務是不會回滾的。 -
PROPAGATION_SUPPORTS
假設ServiceB.methodB() 的事務級別為 PROPAGATION_SUPPORTS,那么當執行到ServiceB.methodB()時,如果發現ServiceA.methodA()已經開啟了一個事務,則加入當前的事務,如果發現ServiceA.methodA()沒有開啟事務,則自己也不開啟事務。這種時候,內部方法的事務性完全依賴於最外層的事務。
剩下幾種就不多介紹,可以參考這篇文章。https://www.jianshu.com/p/249f2cd42692
-
readOnly()
事務超時設置.超過這個時間,發生回滾 -
readOnly()
只讀事務
從這一點設置的時間點開始(時間點a)到這個事務結束的過程中,其他事務所提交的數據,該事務將看不見!(查詢中不會出現別人在時間點a之后提交的數據)。
注意是一次執行多次查詢來統計某些信息,這時為了保證數據整體的一致性,要用只讀事務 -
rollbackFor()
導致事務回滾的異常類數組. -
rollbackForClassName()
導致事務回滾的異常類名字數組 -
noRollbackFor
不會導致事務回滾的異常類數組 -
noRollbackForClassName
不會導致事務回滾的異常類名字數組
3. @Transactional
使用應該注意的地方
3.1 默認情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務;除此之外,Spring 不會回滾事務。你如果想要在特定的異常回滾可以考慮rollbackFor()
等屬性
3.2 @Transactional
只能應用到 public 方法才有效。
這是因為在使用 Spring AOP 代理時,Spring 會調用 TransactionInterceptor
在目標方法執行前后進行攔截之前,DynamicAdvisedInterceptor
(CglibAopProxy
的內部類)的的 intercept
方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource
(Spring 通過這個類獲取@Transactional
注解的事務屬性配置屬性信息)的 computeTransactionAttribute
方法。
@Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { //這里判斷是否是public方法 if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } //省略其他代碼
若不是 public,就不會獲取@Transactional 的屬性配置信息,最終會造成不會用 TransactionInterceptor 來攔截該目標方法進行事務管理。整個事務執行的時序圖如下。

3.3 Spring 的 AOP 的自調用問題
在 Spring 的 AOP 代理下,只有目標方法由外部調用,目標方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。若同一類中的其他沒有@Transactional
注解的方法內部調用有@Transactional
注解的方法,有@Transactional
注解的方法的事務被忽略,不會發生回滾。這個問題是由於Spring AOP 代理造成的(如下面代碼所示)。之所以沒有應用事務,是因為在內部調用,而代理后的類(把目標類作為成員變量靜態代理)只是調用成員變量中的對應方法,自然也就沒有aop中的advice,造成只能調用父類的方法。另外一個問題是只能應用在public方法上。為解決這兩個問題,使用 AspectJ 取代 Spring AOP 代理。
@Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("mask"); logger.info("save the user{}",user); userRepository.save(user); // throw new RuntimeException("exception"); } public void saveUserBack(){ saveUser(); //自調用發生 }
3.4 自注入解決辦法
@Service public class UserService { Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired UserRepository userRepository; @Autowired UserService userService; //自注入來解決 @Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("mask"); logger.info("save the user{}",user); userRepository.save(user); // throw new RuntimeException("exception"); } public void saveUserBack(){ saveUser(); } }
另外也可以把注解加到類上來解決。
3.4注意事項
@Transactional
注解不支持多數據源的情況- 如果存在多個數據源且未指定具體的事務管理器,那么實際上啟用的事務管理器是最先在配置文件中指定的(即先加載的)
- Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。
4.spring事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。
還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。
4. 總結
@Transactional
用起來是方便,但是我們需要明白它背后的原理,避免入坑。另外@Transactional
不建議用在處理時間過長的事務。因為,它會一直持有數據庫線程池的連接,造成不能及時返回。就是盡量是的事務的處理時間短。
參考文章:
Spring @Transactional的使用及原理
深入理解 Spring 事務原理
透徹的掌握 Spring 中@transactional 的使用
在同一個類中,一個方法調用另外一個有注解(比如@Async,@Transational)的方法,注解失效的原因和解決方法
https://www.cnblogs.com/yepei/p/4716112.html
作者:maskwang520
鏈接:https://www.jianshu.com/p/5687e2a38fbc
來源:簡書
@Transactional 詳解
@Transactional 是聲明式事務管理 編程中使用的注解
1 .添加位置
1)接口實現類或接口實現方法上,而不是接口類中。
2)訪問權限:public 的方法才起作用。@Transactional 注解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。
系統設計:將標簽放置在需要進行事務管理的方法上,而不是放在所有接口實現類上:只讀的接口就不需要事務管理,由於配置了@Transactional就需要AOP攔截及事務的處理,可能影響系統性能。
3)錯誤使用:
1.接口中A、B兩個方法,A無@Transactional標簽,B有,上層通過A間接調用B,此時事務不生效。 2.接口中異常(運行時異常)被捕獲而沒有被拋出。 默認配置下,spring 只有在拋出的異常為運行時 unchecked 異常時才回滾該事務, 也就是拋出的異常為RuntimeException 的子類(Errors也會導致事務回滾), 而拋出 checked 異常則不會導致事務回滾 。可通過 @Transactional rollbackFor進行配置。 3.多線程下事務管理因為線程不屬於 spring 托管,故線程不能夠默認使用 spring 的事務, 也不能獲取spring 注入的 bean 。 在被 spring 聲明式事務管理的方法內開啟多線程,多線程內的方法不被事務控制。 一個使用了@Transactional 的方法,如果方法內包含多線程的使用,方法內部出現異常, 不會回滾線程中調用方法的事務。
2.聲明式事務管理實現方式:
基於 tx 和 aop 名字空間的 xml 配置文件
// 基本配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd"> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="shardingDataSource"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> // MyBatis 自動參與到 spring 事務管理中,無需額外配置, 只要 org.mybatis.spring.SqlSessionFactoryBean 引用的數據源與 DataSourceTransactionManager 引用的數據源一致即可,否則事務管理會不起作用。 // <annotation-driven> 標簽的聲明, 是在 Spring 內部啟用 @Transactional 來進行事務管理,使用 @Transactional 前需要配置。
3. @Transactional注解
@Transactional 實質是使用了 JDBC 的事務來進行事務控制的
@Transactional 基於 Spring 的動態代理的機制
@Transactional 實現原理:
1) 事務開始時,通過AOP機制,生成一個代理connection對象,
並將其放入 DataSource 實例的某個與 DataSourceTransactionManager 相關的某處容器中。
在接下來的整個事務中,客戶代碼都應該使用該 connection 連接數據庫,
執行所有數據庫命令。
[不使用該 connection 連接數據庫執行的數據庫命令,在本事務回滾的時候得不到回滾]
(物理連接 connection 邏輯上新建一個會話session;
DataSource 與 TransactionManager 配置相同的數據源)
2) 事務結束時,回滾在第1步驟中得到的代理 connection 對象上執行的數據庫命令,
然后關閉該代理 connection 對象。
(事務結束后,回滾操作不會對已執行完畢的SQL操作命令起作用)
4.聲明式事務的管理實現本質:
事務的兩種開啟方式:
顯示開啟 start transaction | begin,通過 commit | rollback 結束事務
關閉數據庫中自動提交 autocommit set autocommit = 0;MySQL 默認開啟自動提交;通過手動提交或執行回滾操作來結束事務
Spring 關閉數據庫中自動提交:在方法執行前關閉自動提交,方法執行完畢后再開啟自動提交
// org.springframework.jdbc.datasource.DataSourceTransactionManager.java 源碼實現
// switch to manual commit if necessary. this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getautocommit()) {
txobject.setmustrestoreautocommit(true);
if (logger.isdebugenabled()) {
logger.debug("switching jdbc connection [" + con + "] to manual commit");
}
con.setautocommit(false);
}
問題:
關閉自動提交后,若事務一直未完成,即未手動執行 commit 或 rollback 時如何處理已經執行過的SQL操作?
C3P0 默認的策略是回滾任何未提交的事務
C3P0 是一個開源的JDBC連接池,它實現了數據源和 JNDI 綁定,支持 JDBC3 規范和 JDBC2 的標准擴展。目前使用它的開源項目有 Hibernate,Spring等
JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種標准的Java命名系統接口,JNDI提供統一的客戶端API,通過不同的訪問提供者接口JNDI服務供應接口(SPI)的實現,由管理者將JNDI API映射為特定的命名服務和目錄系統,使得Java應用程序可以和這些命名服務和目錄服務之間進行交互
-------------------------------------------------------------------------------------------------------------------------------
5. 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. @Transactional(isolation = Isolation.READ_UNCOMMITTED):讀取未提交數據(會出現臟讀,
不可重復讀) 基本不使用
2. @Transactional(isolation = Isolation.READ_COMMITTED):讀取已提交數據(會出現不可重復讀和幻讀)
3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重復讀(會出現幻讀)
4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化
事務傳播行為:如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為
1. TransactionDefinition.PROPAGATION_REQUIRED:
如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
創建一個新的事務,如果當前存在事務,則把當前事務掛起。
3. TransactionDefinition.PROPAGATION_SUPPORTS:
如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事務方式運行,如果當前存在事務,則把當前事務掛起。
5. TransactionDefinition.PROPAGATION_NEVER:
以非事務方式運行,如果當前存在事務,則拋出異常。
6. TransactionDefinition.PROPAGATION_MANDATORY:
如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
7. TransactionDefinition.PROPAGATION_NESTED:
如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;
如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
上表字段說明:
1. value :主要用來指定不同的事務管理器;
主要用來滿足在同一個系統中,存在不同的事務管理器。
比如在Spring中,聲明了兩種事務管理器txManager1, txManager2.然后,
用戶可以根據這個參數來根據需要指定特定的txManager.
2. value 適用場景:在一個系統中,需要訪問多個數據源或者多個數據庫,
則必然會配置多個事務管理器的
3. REQUIRED_NEW:內部的事務獨立運行,在各自的作用域中,可以獨立的回滾或者提交;
而外部的事務將不受內部事務的回滾狀態影響。
4. ESTED 的事務,基於單一的事務來管理,提供了多個保存點。
這種多個保存點的機制允許內部事務的變更觸發外部事務的回滾。
而外部事務在混滾之后,仍能繼續進行事務處理,即使部分操作已經被混滾。
由於這個設置基於 JDBC 的保存點,所以只能工作在 JDB C的機制。
5. rollbackFor:讓受檢查異常回滾;即讓本來不應該回滾的進行回滾操作。
6. noRollbackFor:忽略非檢查異常;即讓本來應該回滾的不進行回滾操作。
6.其他:
1. 事務方法的嵌套調用會產生事務傳播。
2. spring 的事務管理是線程安全的
3. 父類的聲明的 @Transactional 會對子類的所有方法進行事務增強;
子類覆蓋重寫父類方式可覆蓋其 @Transactional 中的聲明配置。
4. 類名上方使用 @Transactional,類中方法可通過屬性配置來覆蓋類上的 @Transactional 配置;
比如:類上配置全局是可讀寫,可在某個方法上改為只讀。
如果不對運行時異常進行處理,那么出現運行時異常之后,要么是線程中止,要么是主程序終止。
如果不想終止,則必須捕獲所有的運行時異常,決不讓這個處理線程退出。隊列里面出現異常數據了,正常的處理應該是把異常數據舍棄,然后記錄日志。不應該由於異常數據而影響下面對正常數據的處理。
非運行時異常是RuntimeException以外的異常,類型上都屬於Exception類及其子類。如IOException、SQLException等以及用戶自定義的Exception異常。對於這種異常,JAVA編譯器強制要求我們必需對出現的這些異常進行catch並處理,否則程序就不能編譯通過。所以,面對這種異常不管我們是否願意,只能自己去寫一大堆catch塊去處理可能的異常。
---------------------
轉自:https://blog.csdn.net/mingyundezuoan/article/details/79017659
https://www.cnblogs.com/clwydjgs/p/9317849.html