Spring事務什麼時候會失效?

面試官:Spring事務什麼時候會失效?

應聘者:

  • 訪問權限問題

  • 方法用final修飾

  • 未被Spring管理

  • 錯誤的傳播特性

  • 自己吞了異常

  • 手動拋了別的異常

  • 自定義了回滾異常

  • 方法內部調用

 

1、訪問權限問題

Java的訪問權限主要有三種:private、protected、public,它們的權限從左到右,依次變大。但如果我們在開發過程中,把有某些事務方法,定義了錯誤的訪問權限,就會導致事務功能出問題,例如:

1 @Service
2 public class OrderService {
3   @Transactional
4     private void add(OrderVO orderVO) {
5       saveData(orderVO);
6   }
7 }

從上面可以看到add方法的訪問權限是private修飾的,這樣會導致事務失效,spring要求被代理方法必須是public的,事務會失效。

 

2、方法final修飾的

當某個方法被final修飾時,子類是無法繼承和重載的,事務是基於動態代理去實現的,如果某個方法用final修飾了,那麼在它的代理類中,就無法重寫該方法,而添加事務功能。

1 @Service
2 public class OrderService {
3   @Transactional
4     public final void add(OrderVO orderVO) {
5        saveData(orderVO);
6   }
7 }

 

3、未被Spring管理

使用spring事務的前提是:對象要被spring管理,需要創建bean實例。如果,你開發了一個Service類,但忘了加@Service註解,比如:

1 //@Service
2 public class OrderService {
3   @Transactional
4     public final void add(OrderVO orderVO) {
5       saveData(orderVO);
6   }
7 }

又或者XML裏面配置納入Spring管理的包文件路徑配置錯誤等。

 

4、錯誤的傳播特性

我們在使用@Transactional註解時,是可以指定propagation參數的。該參數的作用是指定事務的傳播特性,spring目前支持7種傳播特性:

  • REQUIRED:如果當前上下文中存在事務,那麼加入該事務,如果不存在事務,創建一個事務,這是默認的傳播屬性值;

  • SUPPORTS:如果當前上下文存在事務,則支持事務加入事務,如果不存在事務,則使用非事務的方式執行;

  • MANDATORY:如果當前上下文中存在事務,否則拋出異常;

  • REQUIRES_NEW:每次都會新建一個事務,並且同時將上下文中的事務掛起,執行當前新建事務完成以後,上下文事務恢復再執行;

  • NOT_SUPPORTED:如果當前上下文中存在事務,則掛起當前事務,然後新的方法在沒有事務的環境中執行;

  • NEVER:如果當前上下文中存在事務,則拋出異常,否則在無事務環境上執行代碼;

  • NESTED:如果當前上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務;

如果我們在手動設置propagation參數的時候,把傳播特性設置錯了,比如:

1 @Service
2 public class OrderService {
3     @Transactional(propagation = Propagation.NEVER)
4     public void add(OrderVO orderVO) {
5          saveData(orderVO);
6     }

 

5、自己吞了異常

事務不會回滾,最常見的問題是:開發者在代碼中手動try…catch了異常。比如:

 1 @Service
 2 public class OrderService {
 3   @Transactional
 4     public void add(OrderVO orderVO) {
 5         try{
 6             saveData(orderVO);
 7         } catch(Exception e) {
 8             log.error(e);
 9      } 
10  }
11 }

這種情況下spring事務當然不會回滾,因為開發者自己捕獲了異常,又沒有手動拋出,換句話說就是把異常吞掉了。

如果想要spring事務能夠正常回滾,必須拋出它能夠處理的異常。如果沒有拋異常,則spring認為程序是正常的。

 

6、手動拋了別的異常

即使開發者沒有手動捕獲異常,但如果拋的異常不正確,spring事務也不會回滾。

 1 @Service
 2 public class OrderService {
 3   @Transactional
 4     public void add(OrderVO orderVO) {
 5         try{
 6             saveData(orderVO);
 7         } catch(Exception e) {
 8             log.error(e);
 9             throw new Exception(e);
10     }   
11  }
12 }

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

 

7、自定義了回滾異常

@Transactional註解聲明事務時,有時我們想自定義回滾的異常,spring也是支持的。可以通過設置rollbackFor參數,來完成這個功能。

1 @Service
2 public class OrderService {
3     @Transactional(rollbackFor = BusinessException.class)
4     public void add(OrderVO orderVO) {
5         saveData(orderVO);
6   }
7 }

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

 

8、方法內部調用

有時候我們需要在某個Service類的某個方法中,調用另外一個事務方法,比如:

 1 @Service
 2 public class OrderService {
 3   @Transactional
 4     public void add(OrderVO orderVO) {
 5          saveData(orderVO);
 6   }
 7 
 8   @Transactional
 9     public void saveData(OrderVO orderVO) {
10          doSameThing();
11   }
12 }

我們看到在事務方法add中,直接調用事務方法saveData。saveData方法擁有事務的能力是因為Spring Aop生成代理了對象,但是這種方法直接調用了this對象的方法,所以saveData方法不會生成事務。

由此可見,在同一個類中的方法直接內部調用,會導致事務失效。