Spring service層的事務探究(Transaction rolled back because it has been marked as rollback-only)

1 背景

代碼在一個service層的事務方法中捕獲並解決另一個事務方法拋出的異常,想要以一個正常信息返回到事務外,即controller層。

示例代碼如下:

controller層代碼

@Controller
public class TestController {

    @Resource
    private TestService testService;

    @RequestMapping(value="/test", produces="application/json;charset=UTF-8")
    @ResponseBody
    public String test() {
        try {
            String mes = testService.testService();
            return "{\"code\":1, \"message\":mes}";
        } catch (ParameterException e) {
            return "{\"code\":0, \"message\":e.getMessage()}";
        } catch (Exception e) {
            return "{\"code\":0, \"message\":\"失敗\"}";
        }
    }
}

service層代碼

@Service
@Transactional
public class TestService {

    @Resource
    private TestService testService;

    public String testService() {
        try {
            testService.testException();
        } catch (ParameterException e) {
            return e.getMessage();
        } catch (Exception e) {
            return "報錯返回";
        }
        return "成功返回";
    }

    public String testException() throws Exception {
        if (true) {
            throw new ParameterException("報參數錯誤返回");
        }
        return "gg";
    }
}

ParameterException是我自己定義的異常類,從上面代碼可以看到,我們預期的結果應該是這樣的:

1.在 testException() 方法中拋出 ParameterException 異常開始,異常信息是:報參數錯誤返回

2.異常拋出到 testService() 方法中就給消化解決了,並且返回拋上來的異常信息:報參數錯誤返回

3.返回的異常信息到controller層,並被成功返回到頁面上: {"code":1, "message":"報參數錯誤返回"} 。

然而事實並非如此,而是直接報錯: Transaction rolled back because it has been marked as rollback-only ,最後返回:{"code":0, "message":"失敗"}

 

2 原因說明

在service層拋出異常,到返回數據到controller層,Spring到底做了什麼呢?我們可以自己從 throw new ParameterException("報參數錯誤返回"); 處開始打斷點一個一個地看,這裏就不仔細分析了。

將代碼分析轉變爲文字,在全局配置了事務的傳播機制是REQUIRED(或明確將事務設置爲了RollbackOnly),在 testException() 方法以事務方式運行時拋出的 非受檢異常 被上一層的事務方法 testService() 消化解決了,沒有繼續拋出去,而運行完 testService() 方法並準備正常返回信息時,Spring會將這個事務進行一次commit,但是此時並不能正常commit,而是進行了rollback操作,並且在事務邊界到達時拋出了UnexpectedRollbackException異常。

總的來說就是:拋出的非受檢異常被處理了,導致設置了RollbackOnly的事務無法正常commit和rollback操作。

 

3 解決方法

這裏的解決方法有如下幾種:

1.在事務中的非受檢異常(即自定義的事務異常)不要在事務中自己解決掉,而是繼續拋出去到事務邊界外(這裏即controller層)再來解決。

2.若確實需要在事務中消化它,可以解決一個正常受檢異常(如:Exception),讓事務commit操作可以正常進行。

3.將處理非受檢異常的事務方法用註解 @Transactional(propagation = Propagation.NOT_SUPPORTED) 配置爲此方法以非事務方式運行,這樣就不會另外執行其他操作了。

 

4 事務擴展

4.1 事務傳播方式

@Transactional註解中可以配置propagation指定事務傳播方式,spring的Propagation提供瞭如下事務傳播屬性:

  • REQUIRED
    進入當前事務,如果沒有事務則創建新的事務
  • SUPPORTS
    支持當前事務狀態,如果有事務則進入,沒有則無事務方式運行
  • MANDATORY
    當前必須有事務,如果沒有則拋異常
  • REQUIRES_NEW
    無論如何都創建新的事務
  • NOT_SUPPORTED
    非事務方式運行
  • NEVER
    非事務方式運行,如果當前有事務則拋出異常
  • NESTED
    新建一個事務,如果當前事務存在,則以嵌套事務運行

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章