一、爲什麼要加事務
工作中我發現很多人其實是不用事務的,那這個肯定會存在隱藏風險。凡是和錢掛鉤的項目一旦出現問題,後果就不堪設想了。
而普通項目中我們也需要確保數據的一致性等問題。你是否也覺得在方法上加上@Transactional不就可以了麼,來看完這篇博文吧。
二、什麼是事務
我在大學的《數據庫原理》中也是學過的,後來畢業面試也遇到過。
事務:是數據庫操作的最小工作單元,是作爲單個邏輯工作單元執行的一系列操作;這些操作作爲一個整體一起向系統提交,要麼都執行、要麼都不執行;
事務的四大特性:
- 原子性 事務是數據庫的邏輯工作單位,事務中包含的各操作要麼都做,要麼都不做 。
- 一致性 事務執行的結果必須是使數據庫從一個一致性狀態變到另一個一致性狀態。因此當數據庫只包含成功事務提交的結果時,就說數據庫處於一致性狀態。如果數據庫系統 運行中發生故障,有些事務尚未完成就被迫中斷,這些未完成事務對數據庫所做的修改有一部分已寫入物理數據庫,這時數據庫就處於一種不正確的狀態,或者說是 不一致的狀態。
- 隔離性 一個事務的執行不能其它事務干擾。即一個事務內部的操作及使用的數據對其它併發事務是隔離的,併發執行的各個事務之間不能互相干擾。
- 持續性 也稱永久性,指一個事務一旦提交,它對數據庫中的數據的改變就應該是永久性的。接下來的其它操作或故障不應該對其執行結果有任何影響。
而Spring框架提供了很好事務管理機制,主要分爲編程式事務
和聲明式事務
兩種。
編程式事務:是指在代碼中手動的管理事務的提交、回滾等操作,代碼侵入性比較強,如下示例
try {
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
er transactionManager.rollback(status);
thrownew InvoiceApplyException("異常失敗");
}
聲明式事務:基於AOP
面向切面的,它將具體業務與事務處理部分解耦,代碼侵入性很低,所以在實際開發中聲明式事務用的比較多。聲明式事務也有兩種實現方式,一是基於TX
和AOP
的xml配置文件方式,二種就是基於@Transactional
註解了。
@Transactional
public int addStudent(Student student) {
int result = studentMapper.addStudent(student);
return result;
}
三、@Transactional詳解
3.1 @Transactional註解可以作用於哪些地方?
- 作用於類:當把
@Transactional 註解放在類上時,表示所有該類的
public 方法 都配置相同的事務屬性信息。 - 作用於方法:當類配置了
@Transactional
,方法也配置了@Transactional
,方法的事務會 覆蓋 類的事務配置信息。 - 作用於接口:不推薦這種使用方法,因爲一旦標註在Interface上並且配置了Spring AOP 使用CGLib動態代理,將會導致
@Transactional
註解失效
3.2 @Transactional註解有哪些屬性?
下圖可以看到相關的屬性以及默認值
按順序來說:
事務傳播行爲 propagation 默認值爲 Propagation.REQUIRED
Propagation.REQUIRED
:如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。( 也就是說如果A方法和B方法都添加了註解,在默認傳播模式下,A方法內部調用B方法,會把兩個方法的事務合併爲一個事務 )Propagation.SUPPORTS
:如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。Propagation.MANDATORY
:如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。Propagation.REQUIRES_NEW
:重新創建一個新的事務,如果當前存在事務,暫停當前的事務。( 當類A中的 a 方法用默認Propagation.REQUIRED
模式,類B中的 b方法加上採用Propagation.REQUIRES_NEW
模式,然後在 a 方法中調用 b方法操作數據庫,然而 a方法拋出異常後,b方法並沒有進行回滾,因爲Propagation.REQUIRES_NEW
會暫停 a方法的事務 )Propagation.NOT_SUPPORTED
:以非事務的方式運行,如果當前存在事務,暫停當前的事務。Propagation.NEVER
:以非事務的方式運行,如果當前存在事務,則拋出異常。Propagation.NESTED
:和 Propagation.REQUIRED 效果一樣。
事務隔離級別isolation 默認值爲 Isolation.DEFAULT
- TransactionDefinition.ISOLATION_DEFAULT: 使用後端數據庫默認的隔離級別,Mysql 默認採用的 REPEATABLE_READ隔離級別 Oracle 默認採用的 READ_COMMITTED隔離級別.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別
timeout 事務的超時時間,默認值爲 -1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。
readOnly 指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。
rollbackFor 用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。
noRollbackFor 拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。
四、舉例
在service中寫一個除零異常的方法,和一個添加數據的方法,但是不加事務註解
@Override
public int addStudent(Student student) {
int result = studentMapper.addStudent(student);
test();
return result;
}
@Override
public int test() {
int a = 10/0;
return a;
}
結果就是報了異常,但是數據還是插入表中了
加上@Transactional註解之後,會進行回滾
五、@Transactional失效場景
這也是面試喜歡問的點
5.1 @Transactional 應用在非 public 修飾的方法上
如果Transactional
註解應用在非public
修飾的方法上,Transactional將會失效。之所以會失效是因爲在Spring AOP 代理時,如上圖所示 TransactionInterceptor
(事務攔截器)在目標方法執行前後進行攔截,DynamicAdvisedInterceptor
(CglibAopProxy 的內部類)的 intercept 方法或 JdkDynamicAopProxy
的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource
的 computeTransactionAttribute
方法,獲取Transactional 註解的事務配置信息。 看一下源碼:很明顯它會檢查目標方法的修飾符是否爲 public,不是 public則不會獲取@Transactional
的屬性配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
5.2 @Transactional 註解屬性 rollbackFor 設置錯誤
rollbackFor
可以指定能夠觸發事務回滾的異常類型。Spring默認拋出了未檢查unchecked
異常(繼承自 RuntimeException
的異常)或者 Error
纔回滾事務;其他異常不會觸發回滾事務。如果在事務中拋出其他類型的異常,但卻期望 Spring 能夠回滾事務,就需要指定 rollbackFor 屬性,如果未指定 rollbackFor 屬性則事務不會回滾。
//自定義異常回滾
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= CustomException.class)
5.3 同一個類中方法調用,導致 @Transactional 失效
開發中避免不了會對同一個類裏面的方法調用,比如有一個類Test,它的一個方法A,A再調用本類的方法B(不論方法B是用public還是private修飾),但方法A沒有聲明註解事務,而B方法有。則外部調用方法A之後,方法B的事務是不會起作用的。這也是經常犯錯誤的一個地方。那爲啥會出現這種情況?其實這還是由於使用 Spring AOP
代理造成的,因爲 只有當事務方法被 當前類以外的代碼 調用時,纔會由Spring
生成的代理對象來管理。
5.4 異常被catch,導致@Transactional失效
spring
的事務是在調用業務方法之前開始的,業務方法執行完畢之後才執行commit
or rollback
,事務是否執行取決於是否拋出runtime異常
。如果拋出runtime exception
並在你的業務方法中沒有catch到的話,事務會回滾。在業務方法中一般不需要catch異常,如果非要catch一定要拋出throw new RuntimeException()
,或者註解中指定拋異常類型@Transactional(rollbackFor=Exception.class)
,否則會導致事務失效,數據commit造成數據不一致