Spring MVC Controller層事物註解不生效
- 2020 年 4 月 2 日
- 筆記
最近在寫一個管理台頁面,是從頁面提交多個form到controller層的,這些form要麼都能提交成功,要麼都失敗。controller層需要進行事物處理,於是簡單的加了@Transactional註解,測試的時候,我故意把最後一個表單的某個欄位長度設置超長,後台肯定會報data too long exception。程式碼主體簡要如下:
@RestController @RequestMapping("/chart") @Transactional public class ChartController { @RequestMapping(value = "/addPie", method = RequestMethod.POST) public ResponseEntity addPie(@RequestBody ReqPieDto pieDto) { try { weChatService.insertCharData(wxChart); wxPieService.insertWxPie(pieData); wxConditionService.insertWxCondition(conditions); } catch (Exception e) { rsp=new ResponseEntity("fail", HttpStatus.GONE); logger.error("pie chart config fail:",e); } return rsp; } } 這個程式碼存在很明顯的問題,首先對Spring的事物機制沒有理解。默認spring事務只在發生未被捕獲的 runtimeexcetpion時才回滾,spring aop異常捕獲原理:被攔截的方法需顯式拋出異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認情況下aop只捕獲runtimeexception的異常,但可以通過配置來捕獲特定的異常並回滾。換句話說在service的方法中不使用try catch 或者在catch中最後加上throw new runtimeexcetpion(),這樣程式異常時才能被aop捕獲進而回滾。 解決方案: 方案1.例如service層處理事務,那麼service中的方法中不做異常捕獲,或者在catch語句中最後增加throw new RuntimeException()語句,以便讓aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理 方案2.在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常。 那就修改程式碼,Controller層修改後程式碼如下: @RestController @RequestMapping("/chart") @Transactional public class ChartController { @RequestMapping(value = "/addPie", method = RequestMethod.POST) public ResponseEntity addPie(@RequestBody ReqPieDto pieDto) { try { weChatService.insertCharData(wxChart); wxPieService.insertWxPie(pieData); wxConditionService.insertWxCondition(conditions); } catch (Exception e) { rsp=new ResponseEntity("fail", HttpStatus.GONE); logger.error("pie chart config fail:",e); throw new SystemException("添加餅圖配置失敗"); } return rsp; } } Service層程式碼也要拋出異常: public void insertWxCondition(WxConditions conditions){ try { mapper.insertSelective(conditions); } catch (Exception e) { logger.error("insert into report config conditions data fail"); throw new SystemException("insert into report config conditions data fail", e); } } 這時候,事物是回滾了,但是頁面顯示的返回結果卻是這樣的: 到這裡,事物問題雖然解決了,但是頁面的返回資訊太不友好了。這是因為Controller方法拋出異常後,程式就中斷了,中斷後,直接把異常拋給前台頁面了。如此看來,在Contrller層進行這種事物處理的時候,既要保證事物的執行,又不要拋出異常、返回自定義消息給前台頁面,這二者不可兼得。那就只有一個辦法了,把3個service封裝到另外一個service層進行事物控制,然後拋出異常,程式碼如下: public void insertPieCharData(ReqPieDto reqPieDto) { try { this.insertCharData(wxChart); wxPieService.insertWxPie(pieData); wxConditionService.insertWxCondition(conditions); } } catch (Exception e) { logger.error("pie chart config fail:",e); throw new SystemException("添加餅圖配置失敗"); } } 然後Controller層去掉trasaction註解,否則異常資訊還是會被拋到前台頁面,在catch exception中處理異常,程式碼如下: @RequestMapping(value = "/addPie", method = RequestMethod.POST) public ResponseEntity addPie(@RequestBody ReqPieDto reqPieDto) { logger.info("receive request pie config dto:{}",JsonUtil.toFullJson(reqPieDto)); ResponseEntity rsp=new ResponseEntity("SUCCESS", HttpStatus.OK); try { wxChartService.insertPieCharData(reqPieDto); } catch (Exception e) { rsp=new ResponseEntity("系統異常", HttpStatus.BAD_REQUEST); } logger.info("返回消息:{}",JsonUtil.toFullJson(rsp)); return rsp; } 問題搞定。