Spring事務的一些基本知識(三)--事務不生效,事務不回滾


一、事務不生效

1.訪問權限問題
眾所周知,java 的訪問權限主要有四種:private、default、protected、public,它們的權限從左到右,依次變大。
但如果我們在開發過程中,把某些事務方法,定義了錯誤的訪問權限,就會導致事務功能出問題。
方法的訪問權限被定義成了private,這樣會導致事務失效,spring 要求被代理方法必須是public的。
也就是說,如果我們自定義的事務方法(即目標方法),它的訪問權限不是public,而是 private、default 或 protected 的話,spring 則不會提供事務功能。

2. 方法用 final 修飾
有時候,某個方法不想被子類重寫,這時可以將該方法定義成 final 的。普通方法這樣定義是沒問題的,但如果將事務方法定義成 final,會導致事務失效。
如果你看過 spring 事務的源碼,可能會知道 spring 事務底層使用了 aop,也就是通過 jdk 動態代理或者 cglib,幫我們生成了代理類,在代理類中實現的事務功能。
但如果某個方法用 final 修飾了,那么在它的代理類中,就無法重寫該方法,而添加事務功能。
注意:如果某個方法是 static 的,同樣無法通過動態代理,變成事務方法。

3.方法內部調用
有時候我們需要在某個 Service 類的某個方法(無事務)中,調用另外一個事務方法。
那么問題來了,如果有些場景,確實想在同一個類的某個無事務方法中,調用它自己的另外一個方法,該怎么辦呢?
3.1 新加一個 Service 方法
這個方法非常簡單,只需要新加一個 Service 方法,把 @Transactional 注解加到新 Service 方法上,把需要事務執行的代碼移到新方法中。具體代碼如下:
3.2 在該 Service 類中注入自己
如果不想再新加一個 Service 類,在該 Service 類中注入自己也是一種選擇。具體代碼如下:

@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;

public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}

可能有些人會有這樣的疑問:這種做法會不會出現循環依賴問題?其實 spring ioc 內部的三級緩存保證了它不會出現循環依賴問題。

3.3 通過 AopContent 類
在該 Service 類中使用 AopContext.currentProxy() 獲取代理對象。
上面的方法 2 確實可以解決問題,但是代碼看起來並不直觀,還可以通過在該 Service 類中使用 AOPProxy 獲取代理對象,實現相同的功能。具體代碼如下:

@Servcie
public class ServiceA {

public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}

4.未被 spring 管理
在我們平時開發過程中,有個細節很容易被忽略,即使用 spring 事務的前提是:對象要被 spring 管理,需要創建 bean 實例。
通常情況下,我們通過 @Controller、@Service、@Component、@Repository 等注解,可以自動實現 bean 實例化和依賴注入的功能。
如果有一天,你忘了加 @Service 注解,那么該類不會交給 spring 管理,所以它的 add 方法也不會生成事務。

5.多線程調用
在實際項目開發中,多線程的使用場景還是挺多的。如果 spring 事務用在多線程場景中,會有問題嗎?
這樣會導致兩個方法不在同一個線程中,獲取到的數據庫連接不一樣,從而是兩個不同的事務。如果想 doOtherThing 方法中拋了異常,add 方法也回滾是不可能的。
如果看過 spring 事務源碼的朋友,可能會知道 spring 的事務是通過數據庫連接來實現的。當前線程中保存了一個 map,key 是數據源,value 是數據庫連接。

private static final ThreadLocal<Map<Object, Object>> resources =
              new NamedThreadLocal<>("Transactional resources");

我們說的同一個事務,其實是指同一個數據庫連接,只有擁有同一個數據庫連接才能同時提交和回滾。如果在不同的線程,拿到的數據庫連接肯定是不一樣的,所以是不同的事務。

6.表不支持事務
有時候我們在開發的過程中,發現某張表的事務一直都沒有生效,最好確認一下你使用的那張表,是否支持事務。

7.未開啟事務
有時候,事務沒有生效的根本原因是沒有開啟事務。
如果你使用的是 springboot 項目,那么你很幸運。因為 springboot 通過DataSourceTransactionManagerAutoConfiguration類,已經默默地幫你開啟了事務。
但如果你使用的還是傳統的 spring 項目,則需要在 applicationContext.xml 文件中,手動配置事務相關參數。如果忘了配置,事務肯定是不會生效的。
另外,如果在 pointcut 標簽中的切入點匹配規則,配錯了的話,有些類的事務也不會生效。

二、事務不回滾
1.錯誤的傳播特性
2.自己吞了異常
3.手動拋了非RuntimeException的異常
即使開發者沒有手動捕獲異常,但如果拋的異常不正確,spring 事務也不會回滾。

@Slf4j
@Service
public class UserService {

@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}

上面的這種情況,開發人員自己捕獲了異常,又手動拋出了異常:Exception,事務同樣不會回滾。
因為 spring 事務,默認情況下只會回滾RuntimeException(運行時異常)和Error(錯誤),對於普通的 Exception(非運行時異常),它不會回滾。
所以自定義異常時,還是繼承RuntimeException

4.異常不匹配
在使用 @Transactional 注解聲明事務時,有時我們想自定義回滾的異常,spring 也是支持的。可以通過設置rollbackFor參數,來完成這個功能。
但如果這個參數的值設置錯了,就會引出一些莫名其妙的問題,例如:

@Slf4j
@Service
public class UserService {

@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}

如果在執行上面這段代碼,保存和更新數據時,程序報錯了,拋了 SqlException、DuplicateKeyException 等異常。而 BusinessException 是我們自定義的異常,報錯的異常不屬於 BusinessException,所以事務也不會回滾。

5.嵌套事務回滾多了

外層調用內層的@Transactional時,內層拋了異常,結果外層也被動拋了異常導致整個事務回滾,原本可能只是希望回滾內部的事務。

【參考】

https://blog.csdn.net/hanjiaqian/article/details/120501741


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM