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

1、事務(wù)簡單介紹

Spring 為事務(wù)管理提供了豐富的功能支持律秃。Spring 事務(wù)管理分為編碼式和聲明式的兩種方式:

1.1 編程式事務(wù)

允許用戶在代碼中精確定義事務(wù)的邊界。編程式事務(wù)管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager玖媚。

對于編程式事務(wù)管理精续,spring推薦使用TransactionTemplate坝锰。

1.2 聲明式事務(wù)

基于AOP,有助于用戶將操作與事務(wù)規(guī)則進(jìn)行解耦。其本質(zhì)是對方法前后進(jìn)行攔截重付,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個事務(wù)顷级,在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。

聲明式事務(wù)管理也有兩種常用的方式堪夭,一種是在配置文件(xml)中做相關(guān)的事務(wù)規(guī)則聲明愕把,另一種是基于@Transactional注解的方式拣凹。顯然基于注解的方式更簡單易用森爽,更清爽。

聲明式事務(wù)管理明顯要優(yōu)于編程式事務(wù)管理嚣镜,這正是spring倡導(dǎo)的非侵入式的開發(fā)方式爬迟。聲明式事務(wù)管理使業(yè)務(wù)代碼不受污染,一個普通的POJO對象菊匿,只要加上注解就可以獲得完全的事務(wù)支持付呕。

和編程式事務(wù)相比,聲明式事務(wù)唯一不足地方是跌捆,后者的最細(xì)粒度只能作用到方法級別徽职,無法做到像編程式事務(wù)那樣可以作用到代碼塊級別。但是即便有這樣的需求佩厚,也存在很多變通的方法姆钉,比如,可以將需要進(jìn)行事務(wù)管理的代碼塊獨立為方法等等。

2潮瓶、@Transactional介紹

可以作用于接口陶冷、接口方法、類以及類方法上毯辅。當(dāng)作用于類上時埂伦,該類的所有 public 方法將都具有該類型的事務(wù)屬性,同時思恐,我們也可以在方法級別使用該標(biāo)注來覆蓋類級別的定義沾谜。

雖然@Transactional 注解可以作用于接口、接口方法胀莹、類以及類方法上类早,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這只有在使用基于接口的代理時它才會生效嗜逻。另外涩僻, @Transactional注解應(yīng)該只被應(yīng)用到 public 方法上,這是由Spring AOP的本質(zhì)決定的栈顷。如果你在 protected逆日、private 或者默認(rèn)可見性的方法上使用 @Transactional 注解,這將被忽略萄凤,也不會拋出任何異常室抽。

默認(rèn)情況下,只有來自外部的方法調(diào)用才會被AOP代理捕獲靡努,也就是坪圾,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務(wù)行為,即使被調(diào)用方法使用@Transactional注解進(jìn)行修飾惑朦。

2.1 @Transactional注解屬性

@Transactional注解里面的各個屬性和Java事務(wù)屬性里面是一一對應(yīng)的兽泄。用來設(shè)置事務(wù)的傳播行為、隔離規(guī)則漾月、回滾規(guī)則病梢、事務(wù)超時、是否只讀梁肿。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * 當(dāng)在配置文件中有多個 TransactionManager , 可以用該屬性指定選擇哪個事務(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ù)超時時間隧期。
     */
    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 {};
}
  • 1、value鸵闪、transactionManager屬性
    它們兩個是一樣的意思檐晕。當(dāng)配置了多個事務(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武通、 rollbackFor、rollbackForClassName珊搀、noRollbackFor冶忱、noRollbackForClassName
    rollbackFor、rollbackForClassName用于設(shè)置那些異常需要回滾境析;noRollbackFor囚枪、noRollbackForClassName用于設(shè)置那些異常不需要回滾。他們就是在設(shè)置事務(wù)的回滾規(guī)則劳淆。

2.2 @Transactional注解的使用

@Transactional注解的使用關(guān)鍵點在理解@Transactional注解里面各個參數(shù)的含義链沼。

@Transactional注解內(nèi)部實現(xiàn)依賴于Spring AOP編程。而AOP在默認(rèn)情況下憔儿,只有來自外部的方法調(diào)用才會被AOP代理捕獲忆植,也就是放可,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務(wù)行為谒臼。

2.2.1 @Transactional 注解必須添加在public方法上,private耀里、protected方法上是無效的

在使用@Transactional 的時候一定要記住蜈缤,在private,protected方法上添加@Transactional 注解不會有任何效果。相當(dāng)于沒加一樣冯挎。即使外部能調(diào)到protected的方法也無效底哥。和沒有添加@Transactional一樣。

2.2.2 函數(shù)之間相互調(diào)用

2.2.2.1 同一個類中函數(shù)相互調(diào)用

同一個類AClass中,有兩個函數(shù)aFunction趾徽、aInnerFunction续滋。aFunction調(diào)用aInnerFunction。而且aFunction函數(shù)會被外部調(diào)用孵奶。

  • 場景1 :
    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ù)都會回滾。
  • 場景2:兩個函數(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)用才有效灌诅。
  • 場景3: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注解添加和沒添加一樣盯仪。
  • 場景4:
    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ā)生在當(dāng)有@Transactional注解函數(shù)有對應(yīng)異常拋出時才會回滾梆奈。(當(dāng)然了要看你添加的@Transactional注解有沒有效)。
2.2.2.2 不同類中函數(shù)相互調(diào)用

兩個類AClass称开、BClass鉴裹。AClass類有aFunction、BClass類有bFunction钥弯。AClass類aFunction調(diào)用BClass類bFunction径荔。最終會在外部調(diào)用AClass類的aFunction。

  • 場景1: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ù)庫的操作都回滾了荸频。
  • 場景2: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ù)充岛。這種情況下,你可以認(rèn)為事務(wù)rollback了兩次。兩個函數(shù)都有異常。
  • 場景3: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ù)被標(biāo)記了只能rollback了尉共。程序繼續(xù)執(zhí)行褒傅,aFunction函數(shù)里面把異常給抓出來了,這個時候aFunction函數(shù)沒有拋出異常袄友,既然你沒有異常那事務(wù)就需要提交殿托,會調(diào)事務(wù)的commit函數(shù)。而之前已經(jīng)標(biāo)記了事務(wù)只能rollback-only(以為是同一個事務(wù))剧蚣。直接就拋異常了支竹,不讓調(diào)了。
  • 場景4:
    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ù)了。

3示辈、總結(jié)

  • 要知道@Transactional注解里面每個屬性的含義寥茫。@Transactional注解屬性就是來控制事務(wù)屬性的。通過這些屬性來生成事務(wù)矾麻。

  • 要明確我們添加的@Transactional注解會不會起作用纱耻。@Transactional注解在外部調(diào)用的函數(shù)上才有效果芭梯,內(nèi)部調(diào)用的函數(shù)添加無效,要切記弄喘。這是由AOP的特性決定的玖喘。

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

checked和unchecked異常

  • checked異常:繼承自java.lang.Exception(java.lang.RuntimeException除外)
    表示無效澎媒,不是程序中可以預(yù)測的。
    比如無效的用戶輸入波桩,文件不存在戒努,網(wǎng)絡(luò)或者數(shù)據(jù)庫連接錯誤,這些都是外在的原因突委,都不是程序內(nèi)部可以控制的柏卤。
    必須在代碼中顯式地處理,比如try-catch塊處理匀油,或者給所在的方法加上throws說明缘缚,將異常拋給調(diào)用站的上一層
  • unchecked異常:繼承自java.lang.RuntimeException(而java.lang.RuntimeException繼承自java.lang.Exception)
    表示錯誤,程序的邏輯錯誤敌蚜。比如IllegalArgumentException, NullPointerException和IllegalStateException桥滨。
    不需要在代碼中顯示地捕獲unchecked異常做處理。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弛车,一起剝皮案震驚了整個濱河市齐媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纷跛,老刑警劉巖喻括,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贫奠,居然都是意外死亡唬血,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門唤崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拷恨,“玉大人,你說我怎么就攤上這事谢肾⊥笾叮” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長微姊。 經(jīng)常有香客問我弊决,道長与倡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮馋贤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘犹芹。我一直安慰自己,他們只是感情好屿笼,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布蛔趴。 她就那樣靜靜地躺著鱼蝉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洁奈。 梳的紋絲不亂的頭發(fā)上低矮,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼税课。 笑死陆馁,一個胖子當(dāng)著我的面吹牛击狮,可吹牛的內(nèi)容都是我干的捺萌。 我是一名探鬼主播酷誓,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼帚屉,長吁一口氣:“原來是場噩夢啊……” “哼琐旁!你這毒婦竟也來了涮阔?” 一聲冷哼從身側(cè)響起猜绣,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤灰殴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掰邢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牺陶,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年辣之,在試婚紗的時候發(fā)現(xiàn)自己被綠了掰伸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡怀估,死狀恐怖狮鸭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情多搀,我是刑警寧澤歧蕉,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站康铭,受9級特大地震影響惯退,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜从藤,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一催跪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夷野,春花似錦懊蒸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春者娱,著一層夾襖步出監(jiān)牢的瞬間抡笼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工黄鳍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留推姻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓框沟,卻偏偏與公主長得像藏古,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子忍燥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

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