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方法不會生成事務。
由此可見,在同一個類中的方法直接內部調用,會導致事務失效。