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異常做處理。