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