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