Spring 事務(wù) -- @Transactional的使用

一、事務(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é):

  1. 要知道@Transactional注解里面每個(gè)屬性的含義。@Transactional注解屬性就是來(lái)控制事務(wù)屬性的。通過(guò)這些屬性來(lái)生成事務(wù)。

  2. 要明確我們添加的@Transactional注解會(huì)不會(huì)起作用。@Transactional注解在外部調(diào)用的函數(shù)上才有效果,內(nèi)部調(diào)用的函數(shù)添加無(wú)效,要切記。這是由AOP的特性決定的。

  3. 要明確事務(wù)的作用范圍,有@Transactional的函數(shù)調(diào)用有@Transactional的函數(shù)的時(shí)候,進(jìn)入第二個(gè)函數(shù)的時(shí)候是新的事務(wù),還是沿用之前的事務(wù)。稍不注意就會(huì)拋UnexpectedRollbackException異常。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,069評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,873評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,128評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,365評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,582評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,821評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,595評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容