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
新建一個事務,如果當前事務存在,則以嵌套事務運行