一、事務(wù)簡單介紹
? ? ? ?事務(wù)指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
1.1 事務(wù)基本要素
- 原子性(Atomicity): 事務(wù)開始后所有操作,要么全部做完,要么全部不做,不可能停滯在中間環(huán)節(jié)。事務(wù)執(zhí)行過程中出錯,會回滾到事務(wù)開始前的狀態(tài),所有的操作就像沒有發(fā)生一樣。也就是說事務(wù)是一個不可分割的整體,就像化學中學過的原子,是物質(zhì)構(gòu)成的基本單位。
- 一致性(Consistency): 事務(wù)開始前和結(jié)束后,數(shù)據(jù)庫的完整性約束沒有被破壞。比如A向B轉(zhuǎn)賬,不可能A扣了錢,B卻沒收到。
- 隔離性(Isolation): 同一時間,只允許一個事務(wù)請求同一數(shù)據(jù),不同的事務(wù)之間彼此沒有任何干擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結(jié)束前,B不能向這張卡轉(zhuǎn)賬。
- 持久性(Durability): 事務(wù)完成后,事務(wù)對數(shù)據(jù)庫的所有更新將被保存到數(shù)據(jù)庫,不能回滾。
1.2 Spring事務(wù)屬性
Spring事務(wù)屬性對應(yīng)TransactionDefinition類里面的各個方法。TransactionDefinition類方法如下所示:
public interface TransactionDefinition {
/**
* 返回事務(wù)傳播行為
*/
int getPropagationBehavior();
/**
* 返回事務(wù)的隔離級別,事務(wù)管理器根據(jù)它來控制另外一個事務(wù)可以看到本事務(wù)內(nèi)的哪些數(shù)據(jù)
*/
int getIsolationLevel();
/**
* 事務(wù)超時時間,事務(wù)必須在多少秒之內(nèi)完成
*/
int getTimeout();
/**
* 事務(wù)是否只讀,事務(wù)管理器能夠根據(jù)這個返回值進行優(yōu)化,確保事務(wù)是只讀的
*/
boolean isReadOnly();
/**
* 事務(wù)名字
*/
@Nullable
String getName();
}
? ? ? ?事務(wù)屬性可以理解成事務(wù)的一些基本配置,描述了事務(wù)策略如何應(yīng)用到方法上。事務(wù)屬性包含了5個方面:傳播行為、隔離規(guī)則、回滾規(guī)則、事務(wù)超時、是否只讀。
事務(wù)的產(chǎn)生需要依賴這些事務(wù)屬性。包括我們下面要講到的@Transactional注解的屬性其實就是在設(shè)置這些值。
1.2.1 傳播行為
? ? ? ?當事務(wù)方法被另一個事務(wù)方法調(diào)用時,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運行,也可能開啟一個新事務(wù),并在自己的事務(wù)中運行。Spring定義了七種傳播行為:
| 傳播行為 | 含義 |
|---|---|
| TransactionDefinition.PROPAGATION_REQUIRED | 如果當前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù),則加入到這個事務(wù)中。這是最常見的選擇。 |
| TransactionDefinition.PROPAGATION_SUPPORTS | 支持當前事務(wù),如果當前沒有事務(wù),就以非事務(wù)方式執(zhí)行。 |
| TransactionDefinition.PROPAGATION_MANDATORY | 表示該方法必須在事務(wù)中運行,如果當前事務(wù)不存在,則會拋出一個異常 |
| TransactionDefinition.PROPAGATION_REQUIRED_NEW | 表示當前方法必須運行在它自己的事務(wù)中。一個新的事務(wù)將被啟動。如果存在當前事務(wù),在該方法執(zhí)行期間,當前事務(wù)會被掛起。 |
| TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 表示該方法不應(yīng)該運行在事務(wù)中。如果當前存在事務(wù),就把當前事務(wù)掛起。 |
| TransactionDefinition.PROPAGATION_NEVER | 表示當前方法不應(yīng)該運行在事務(wù)上下文中。如果當前正有一個事務(wù)在運行,則會拋出異常 |
| TransactionDefinition.PROPAGATION_NESTED | 如果當前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當前沒有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作。 |
1.2.2 隔離規(guī)則
? ? ? ?隔離級別定義了一個事務(wù)可能受其他并發(fā)事務(wù)影響的程度。
? ? ? ?在實際開發(fā)過程中,我們絕大部分的事務(wù)都是有并發(fā)情況。下多個事務(wù)并發(fā)運行,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(wù)。在這種情況下可能會導(dǎo)致以下的問題:
- 臟讀(Dirty reads)—— 事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù),然后B回滾操作,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)。
- 不可重復(fù)讀(Nonrepeatable read)—— 事務(wù) A 多次讀取同一數(shù)據(jù),事務(wù) B 在事務(wù)A多次讀取的過程中,對數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時,結(jié)果不一致。
- 幻讀(Phantom read)—— 系統(tǒng)管理員A將數(shù)據(jù)庫中所有學生的成績從具體分數(shù)改為ABCDE等級,但是系統(tǒng)管理員B就在這個時候插入了一條具體分數(shù)的記錄,當系統(tǒng)管理員A改結(jié)束后發(fā)現(xiàn)還有一條記錄沒有改過來,就好像發(fā)生了幻覺一樣,這就叫幻讀。
不可重復(fù)讀的和幻讀很容易混淆,不可重復(fù)讀側(cè)重于修改,幻讀側(cè)重于新增或刪除。解決不可重復(fù)讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表
? ? ? ?咱們已經(jīng)知道了在并發(fā)狀態(tài)下可能產(chǎn)生: 臟讀、不可重復(fù)讀、幻讀的情況。因此我們需要將事務(wù)與事務(wù)之間隔離。根據(jù)隔離的方式來避免事務(wù)并發(fā)狀態(tài)下臟讀、不可重復(fù)讀、幻讀的產(chǎn)生。Spring中定義了五種隔離規(guī)則:
| 隔離級別 | 含義 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|---|
| TransactionDefinition.ISOLATION_DEFAULT | 使用后端數(shù)據(jù)庫默認的隔離級別 | |||
| TransactionDefinition.ISOLATION_READ_UNCOMMITTED | 允許讀取尚未提交的數(shù)據(jù)變更(最低的隔離級別) | 是 | 是 | 是 |
| TransactionDefinition.ISOLATION_READ_COMMITTED | 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù) | 否 | 是 | 是 |
| TransactionDefinition.ISOLATION_REPEATABLE_READ | 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改 | 否 | 否 | 是 |
| TransactionDefinition.ISOLATION_SERIALIZABLE | 最高的隔離級別,完全服從ACID的隔離級別,也是最慢的事務(wù)隔離級別,因為它通常是通過完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實現(xiàn)的 | 否 | 否 | 否 |
? ? ? ?ISOLATION_SERIALIZABLE 隔離規(guī)則類型在開發(fā)中很少用到。舉個很簡單的例子。咱們使用了ISOLATION_SERIALIZABLE規(guī)則。A,B兩個事務(wù)操作同一個數(shù)據(jù)表并發(fā)過來了。A先執(zhí)行。A事務(wù)這個時候會把表給鎖住,B事務(wù)執(zhí)行的時候直接報錯。
? ? ? ?補充:
- 事務(wù)隔離級別為ISOLATION_READ_UNCOMMITTED時,寫數(shù)據(jù)只會鎖住相應(yīng)的行。
- 事務(wù)隔離級別為可ISOLATION_REPEATABLE_READ時,如果檢索條件有索引(包括主鍵索引)的時候,默認加鎖方式是next-key鎖;如果檢索條件沒有索引,更新數(shù)據(jù)時會鎖住整張表。一個間隙被事務(wù)加了鎖,其他事務(wù)是不能在這個間隙插入記錄的,這樣可以防止幻讀。
- 事務(wù)隔離級別為ISOLATION_SERIALIZABLE時,讀寫數(shù)據(jù)都會鎖住整張表。
- 隔離級別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對并發(fā)性能的影響也就越大。
1.2.3 回滾規(guī)則
? ? ? ?事務(wù)回滾規(guī)則定義了哪些異常會導(dǎo)致事務(wù)回滾而哪些不會。默認情況下,只有未檢查異常(RuntimeException和Error類型的異常)會導(dǎo)致事務(wù)回滾。而在遇到檢查型異常時不會回滾。 但是你可以聲明事務(wù)在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務(wù)遇到特定的異常不回滾,即使這些異常是運行期異常。
1.2.4 事務(wù)超時
? ? ? ?為了使應(yīng)用程序很好地運行,事務(wù)不能運行太長的時間。因為事務(wù)可能涉及對后端數(shù)據(jù)庫的鎖定,也會占用數(shù)據(jù)庫資源。事務(wù)超時就是事務(wù)的一個定時器,在特定時間內(nèi)事務(wù)如果沒有執(zhí)行完畢,那么就會自動回滾,而不是一直等待其結(jié)束。
1.2.5 是否只讀
? ? ? ?如果在一個事務(wù)中所有關(guān)于數(shù)據(jù)庫的操作都是只讀的,也就是說,這些操作只讀取數(shù)據(jù)庫中的數(shù)據(jù),而并不更新數(shù)據(jù), 這個時候我們應(yīng)該給該事務(wù)設(shè)置只讀屬性,這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)。提升效率。
二、@Transactional使用
? ? ? ?Spring 為事務(wù)管理提供了豐富的功能支持。Spring 事務(wù)管理分為編碼式和聲明式的兩種方式:
編程式事務(wù):允許用戶在代碼中精確定義事務(wù)的邊界。編程式事務(wù)管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于編程式事務(wù)管理,spring推薦使用TransactionTemplate。
聲明式事務(wù): 基于AOP,有助于用戶將操作與事務(wù)規(guī)則進行解耦。其本質(zhì)是對方法前后進行攔截,然后在目標方法開始之前創(chuàng)建或者加入一個事務(wù),在執(zhí)行完目標方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。聲明式事務(wù)管理也有兩種常用的方式,一種是在配置文件(xml)中做相關(guān)的事務(wù)規(guī)則聲明,另一種是基于@Transactional注解的方式。顯然基于注解的方式更簡單易用,更清爽。@Transactional注解的使用也是我們本文著重要理解的部分。
? ? ? ?顯然聲明式事務(wù)管理要優(yōu)于編程式事務(wù)管理,這正是spring倡導(dǎo)的非侵入式的開發(fā)方式。聲明式事務(wù)管理使業(yè)務(wù)代碼不受污染,一個普通的POJO對象,只要加上注解就可以獲得完全的事務(wù)支持。和編程式事務(wù)相比,聲明式事務(wù)唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務(wù)那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務(wù)管理的代碼塊獨立為方法等等。
2.1 @Transactional介紹
? ? ? ?@Transactional注解 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法將都具有該類型的事務(wù)屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。
? ? ? ?雖然@Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這只有在使用基于接口的代理時它才會生效。另外, @Transactional注解應(yīng)該只被應(yīng)用到 public 方法上,這是由Spring AOP的本質(zhì)決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。
? ? ? ?默認情況下,只有來自外部的方法調(diào)用才會被AOP代理捕獲,也就是,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務(wù)行為,即使被調(diào)用方法使用@Transactional注解進行修飾。
2.2 @Transactional注解屬性
? ? ? ?@Transactional注解里面的各個屬性和咱們在上面講的事務(wù)屬性里面是一一對應(yīng)的。用來設(shè)置事務(wù)的傳播行為、隔離規(guī)則、回滾規(guī)則、事務(wù)超時、是否只讀。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 當在配置文件中有多個 TransactionManager , 可以用該屬性指定選擇哪個事務(wù)管理器。
*/
@AliasFor("transactionManager")
String value() default "";
/**
* 同上。
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 事務(wù)的傳播行為,默認值為 REQUIRED。
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事務(wù)的隔離規(guī)則,默認值采用 DEFAULT。
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 事務(wù)超時時間。
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 是否只讀事務(wù)
*/
boolean readOnly() default false;
/**
* 用于指定能夠觸發(fā)事務(wù)回滾的異常類型。
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 同上,指定類名。
*/
String[] rollbackForClassName() default {};
/**
* 用于指定不會觸發(fā)事務(wù)回滾的異常類型
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 同上,指定類名
*/
String[] noRollbackForClassName() default {};
}
2.2.1 value、transactionManager屬性
? ? ? ?它們兩個是一樣的意思。當配置了多個事務(wù)管理器時,可以使用該屬性指定選擇哪個事務(wù)管理器。大多數(shù)項目只需要一個事務(wù)管理器。然而,有些項目為了提高效率、或者有多個完全不同又不相干的數(shù)據(jù)源,從而使用了多個事務(wù)管理器。機智的Spring的Transactional管理已經(jīng)考慮到了這一點,首先定義多個transactional manager,并為qualifier屬性指定不同的值;然后在需要使用@Transactional注解的時候指定TransactionManager的qualifier屬性值或者直接使用bean名稱。配置和代碼使用的例子:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource1"></property>
<qualifier value="datasource1Tx"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource2"></property>
<qualifier value="datasource2Tx"/>
</bean>
public class TransactionalService {
@Transactional("datasource1Tx")
public void setSomethingInDatasource1() { ... }
@Transactional("datasource2Tx")
public void doSomethingInDatasource2() { ... }
}
2.2.2 propagation屬性
? ? ? ?propagation用于指定事務(wù)的傳播行為,默認值為 REQUIRED。propagation有七種類型,就是我們在上文中講到的事務(wù)屬性傳播行為的七種方式,如下所示:
| propagation屬性 | 事務(wù)屬性-傳播行為 | 含義 |
|---|---|---|
| REQUIRED | TransactionDefinition.PROPAGATION_REQUIRED | 如果當前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù),則加入到這個事務(wù)中。這是最常見的選擇。 |
| SUPPORTS | TransactionDefinition.PROPAGATION_SUPPORTS | 支持當前事務(wù),如果當前沒有事務(wù),就以非事務(wù)方式執(zhí)行。 |
| MANDATORY | TransactionDefinition.PROPAGATION_MANDATORY | 表示該方法必須在事務(wù)中運行,如果當前事務(wù)不存在,則會拋出一個異常。 |
| REQUIRES_NEW | TransactionDefinition.PROPAGATION_REQUIRES_NEW | 表示當前方法必須運行在它自己的事務(wù)中。一個新的事務(wù)將被啟動。如果存在當前事務(wù),在該方法執(zhí)行期間,當前事務(wù)會被掛起。 |
| NOT_SUPPORTED | TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 表示該方法不應(yīng)該運行在事務(wù)中。如果當前存在事務(wù),就把當前事務(wù)掛起。 |
| NEVER | TransactionDefinition.PROPAGATION_NEVER | 表示當前方法不應(yīng)該運行在事務(wù)上下文中。如果當前正有一個事務(wù)在運行,則會拋出異常。 |
| NESTED | TransactionDefinition.PROPAGATION_NESTED | 如果當前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當前沒有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作。 |
2.2.3 isolation屬性
? ? ? ?isolation用于指定事務(wù)的隔離規(guī)則,默認值為DEFAULT。@Transactional的隔離規(guī)則和上文事務(wù)屬性里面的隔離規(guī)則也是一一對應(yīng)的??偣参宸N隔離規(guī)則,如下所示:
| @isolation屬性 | 事務(wù)屬性-隔離規(guī)則 | 含義 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|---|---|
| DEFAULT | TransactionDefinition.ISOLATION_DEFAULT | 使用后端數(shù)據(jù)庫默認的隔離級別 | |||
| READ_UNCOMMITTED | TransactionDefinition.ISOLATION_READ_UNCOMMITTED | 允許讀取尚未提交的數(shù)據(jù)變更(最低的隔離級別) | 是 | 是 | 是 |
| READ_COMMITTED | TransactionDefinition.ISOLATION_READ_COMMITTED | 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù) | 否 | 是 | 是 |
| REPEATABLE_READ | TransactionDefinition.ISOLATION_REPEATABLE_READ | 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改 | 否 | 否 | 是 |
| SERIALIZABLE | TransactionDefinition.ISOLATION_SERIALIZABLE | 最高的隔離級別,完全服從ACID的隔離級別,也是最慢的事務(wù)隔離級別,因為它通常是通過完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實現(xiàn)的 | 否 | 否 | 否 |
2.2.4 timeout
? ? ? ?timeout用于設(shè)置事務(wù)的超時屬性。
2.2.5 readOnly
? ? ? ?readOnly用于設(shè)置事務(wù)是否只讀屬性。
2.2.6 rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName
? ? ? ?rollbackFor、rollbackForClassName用于設(shè)置那些異常需要回滾;noRollbackFor、noRollbackForClassName用于設(shè)置那些異常不需要回滾。他們就是在設(shè)置事務(wù)的回滾規(guī)則。
2.3 @Transactional注解的使用
? ? ? ?@Transactional注解的使用關(guān)鍵點在理解@Transactional注解里面各個參數(shù)的含義。這個咱們在上面已經(jīng)對@Transactional注解參數(shù)的各個含義做了一個簡單的介紹。接下來,咱們著重講一講@Transactional注解使用過程中一些注意的點。
? ? ? ?@Transactional注解內(nèi)部實現(xiàn)依賴于Spring AOP編程。而AOP在默認情況下,只有來自外部的方法調(diào)用才會被AOP代理捕獲,也就是,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務(wù)行為。
2.3.1 @Transactional 注解盡量直接加在方法上
? ? ? ?為什么:因為@Transactional直接加在類或者接口上,@Transactional注解會對類或者接口里面所有的public方法都有效(相當于所有的public方法都加上了@Transactional注解,而且注解帶的參數(shù)都是一樣的)。第一影響性能,可能有些方法我不需要@Transactional注解,第二方法不同可能@Transactional注解需要配置的參數(shù)也不同,比如有一個方法只是做查詢操作,那咱們可能需要配置Transactional注解的readOnly參數(shù)。所以強烈建議@Transactional注解直接添加的需要的方法上。
2.3.2 @Transactional 注解必須添加在public方法上,private、protected方法上是無效的
? ? ? ?在使用@Transactional 的時候一定要記住,在private,protected方法上添加@Transactional 注解不會有任何效果。相當于沒加一樣。即使外部能調(diào)到protected的方法也無效。和沒有添加@Transactional一樣。
2.3.3 函數(shù)之間相互調(diào)用
? ? ? ?關(guān)于有@Transactional的函數(shù)之間調(diào)用,會產(chǎn)生什么情況。這里咱們通過幾個例子來說明。
2.3.3.1 同一個類中函數(shù)相互調(diào)用
? ? ? ?同一個類AClass中,有兩個函數(shù)aFunction、aInnerFunction。aFunction調(diào)用aInnerFunction。而且aFunction函數(shù)會被外部調(diào)用。
情況0: aFunction添加了@Transactional注解,aInnerFunction函數(shù)沒有添加。aInnerFunction拋異常。
public class AClass {
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
aInnerFunction(); // 調(diào)用內(nèi)部沒有添加@Transactional注解的函數(shù)
}
private void aInnerFunction() {
//todo: 操作數(shù)據(jù)B(做了增,刪,改 操作)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)操作的數(shù)據(jù)都會回滾。
情況1:兩個函數(shù)都添加了@Transactional注解。aInnerFunction拋異常。
public class AClass {
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
aInnerFunction(); // 調(diào)用內(nèi)部沒有添加@Transactional注解的函數(shù)
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
private void aInnerFunction() {
//todo: 操作數(shù)據(jù)B(做了增,刪,改 操作)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:同第一種情況一樣,兩個函數(shù)對數(shù)據(jù)庫操作都會回滾。因為同一個類中函數(shù)相互調(diào)用的時候,內(nèi)部函數(shù)添加@Transactional注解無效。@Transactional注解只有外部調(diào)用才有效。
情況2: aFunction不添加注解,aInnerFunction添加注解。aInnerFunction拋異常。
public class AClass {
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
aInnerFunction(); // 調(diào)用內(nèi)部沒有添加@Transactional注解的函數(shù)
}
@Transactional(rollbackFor = Exception.class)
protected void aInnerFunction() {
//todo: 操作數(shù)據(jù)B(做了增,刪,改 操作)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)對數(shù)據(jù)庫的操作都不會回滾。因為內(nèi)部函數(shù)@Transactional注解添加和沒添加一樣。
情況3:aFunction添加了@Transactional注解,aInnerFunction函數(shù)沒有添加。aInnerFunction拋異常,不過在aFunction里面把異常抓出來了。
public class AClass {
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
try {
aInnerFunction(); // 調(diào)用內(nèi)部沒有添加@Transactional注解的函數(shù)
} catch (Exception e) {
e.printStackTrace();
}
}
private void aInnerFunction() {
//todo: 操作數(shù)據(jù)B(做了增,刪,改 操作)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)里面的數(shù)據(jù)庫操作都成功。事務(wù)回滾的動作發(fā)生在當有@Transactional注解函數(shù)有對應(yīng)異常拋出時才會回滾。(當然了要看你添加的@Transactional注解有沒有效)。
2.3.3.1. 不同類中函數(shù)相互調(diào)用
? ? ? ?兩個類AClass、BClass。AClass類有aFunction、BClass類有bFunction。AClass類aFunction調(diào)用BClass類bFunction。最終會在外部調(diào)用AClass類的aFunction。
情況0:aFunction添加注解,bFunction不添加注解。bFunction拋異常。
@Service()
public class AClass {
private BClass bClass;
@Autowired
public void setbClass(BClass bClass) {
this.bClass = bClass;
}
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
bClass.bFunction();
}
}
@Service()
public class BClass {
public void bFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)對數(shù)據(jù)庫的操作都回滾了。
情況1:aFunction、bFunction兩個函數(shù)都添加注解,bFunction拋異常。
@Service()
public class AClass {
private BClass bClass;
@Autowired
public void setbClass(BClass bClass) {
this.bClass = bClass;
}
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
bClass.bFunction();
}
}
@Service()
public class BClass {
@Transactional(rollbackFor = Exception.class)
public void bFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)對數(shù)據(jù)庫的操作都回滾了。兩個函數(shù)里面用的還是同一個事務(wù)。這種情況下,你可以認為事務(wù)rollback了兩次。兩個函數(shù)都有異常。
情況2:aFunction、bFunction兩個函數(shù)都添加注解,bFunction拋異常。aFunction抓出異常。
@Service()
public class AClass {
private BClass bClass;
@Autowired
public void setbClass(BClass bClass) {
this.bClass = bClass;
}
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
try {
bClass.bFunction();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service()
public class BClass {
@Transactional(rollbackFor = Exception.class)
public void bFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:兩個函數(shù)數(shù)據(jù)庫操作都沒成功。而且還拋異常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only??创蛴〕鰜淼慕忉屢埠芎美斫獍?。咱們也可以這么理解,兩個函數(shù)用的是同一個事務(wù)。bFunction函數(shù)拋了異常,調(diào)了事務(wù)的rollback函數(shù)。事務(wù)被標記了只能rollback了。程序繼續(xù)執(zhí)行,aFunction函數(shù)里面把異常給抓出來了,這個時候aFunction函數(shù)沒有拋出異常,既然你沒有異常那事務(wù)就需要提交,會調(diào)事務(wù)的commit函數(shù)。而之前已經(jīng)標記了事務(wù)只能rollback-only(以為是同一個事務(wù))。直接就拋異常了,不讓調(diào)了。
情況3:aFunction、bFunction兩個函數(shù)都添加注解,bFunction拋異常。aFunction抓出異常。這里要注意bFunction函數(shù)@Transactional注解我們是有變化的,加了一個參數(shù)propagation = Propagation.REQUIRES_NEW,控制事務(wù)的傳播行為。表明是一個新的事務(wù)。其實咱們情況3就是來解決情況2的問題的。
@Service()
public class AClass {
private BClass bClass;
@Autowired
public void setbClass(BClass bClass) {
this.bClass = bClass;
}
@Transactional(rollbackFor = Exception.class)
public void aFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
try {
bClass.bFunction();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service()
public class BClass {
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bFunction() {
//todo: 數(shù)據(jù)庫操作A(增,刪,該)
throw new RuntimeException("函數(shù)執(zhí)行有異常!");
}
}
? ? ? ?結(jié)果:bFunction函數(shù)里面的操作回滾了,aFunction里面的操作成功了。有了前面情況2的理解。這種情況也很好解釋。兩個函數(shù)不是同一個事務(wù)了。
? ? ? ?關(guān)于@Transactional注解的使用,就說這么些。最后做幾點總結(jié):
要知道@Transactional注解里面每個屬性的含義。@Transactional注解屬性就是來控制事務(wù)屬性的。通過這些屬性來生成事務(wù)。
要明確我們添加的@Transactional注解會不會起作用。@Transactional注解在外部調(diào)用的函數(shù)上才有效果,內(nèi)部調(diào)用的函數(shù)添加無效,要切記。這是由AOP的特性決定的。
要明確事務(wù)的作用范圍,有@Transactional的函數(shù)調(diào)用有@Transactional的函數(shù)的時候,進入第二個函數(shù)的時候是新的事務(wù),還是沿用之前的事務(wù)。稍不注意就會拋UnexpectedRollbackException異常。