文章參考了此博客: https://blog.csdn.net/qq_40594137/article/details/82772545
寫這篇文章之前先說明一下:
1. Controller中添加事務管理,是可行的,但是強烈不推薦,因為不符合實際開發場景,還會導致一系列問題
2. 事務請在Service處理,所有的業務邏輯請寫在 Service, Service中異常請拋出,慎用 try...catch捕獲異常
寫這邊文章的背景:
公司有個老的項目,springMVC + spring + mybatis,事務是在Service層處理的,但是之前的開發人員把很多業務邏輯寫在了 Controller,出現了操作失敗仍然將數據寫入數據庫的bug.....,於是開始研究在 Controller中添加事務管理
Controller中添加事務管理步驟:
1. spring.xml中事務配置不變
2. 在spring-mvc.xml中定義事務配置:
A: 命名空間中 加入約束 不加項目啟動會報異常:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
B: 定義事務注解解析 <tx:annotation-driven transaction-manager="transactionManager" />
3. 在需要控制事務的Controller 類或者方法上使用 @Transactional(rollbackFor = {Exception.class}) ,當出現異常回滾
需要注意的是: Controller層只支持 @Transactional 注解式事務!
關於為什么要在spring-mvc.xml中添加 <tx:annotation-driven transaction-manager="transactionManager" /> 的說明:
錯誤的方式----通過修改spring.xml中的配置來實現在controller中控制事務會發現事務無效,如下:
<aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* net.*.controller.*.*(..))"/></aop:config>
原因如下: spring容器和spring-mvc是父子容器。在服務器啟動時,會先加載web.xml配置文件 ==> 再加載spring配置文件 ==> 再回到web.xml【加載監聽器;加載過濾器;加載前端控制器】==>再加載springMVC配置文件,在Spring配置文件中,我們掃描注冊的是service實現類,就算掃描注冊了controller 也會在后面加載SpringMVC配置文件[掃描注冊controller]覆蓋掉,所以想要在controller中實現事務管理,僅在spring配置文件配置<tx:annotation-driven>或<aop:config>是沒有效果的,必須將事務配置定義在Spring MVC的應用上下文(spring-mvc.xml)中。在spring-framework-reference.pdf文檔中說明了: <tx:annoation-driven/>只會查找和它在相同的應用上下文件中定義的bean上面的@Transactional注解
關於 @Transactional 注解的一些說明:
有篇博客寫的很好,我就直接鏈接了 https://www.jianshu.com/p/befc2d73e487
關於@Transaction 事務不起作用的總結:
@Transaction不起作用的情況:1.靜態(static )方法
2.(private)私有化方法
3.當本類的使用@Transactional的方法被本類的其它沒有開啟事務的方法調用時,不會開啟事務。
使用@Transactional的方法被其它類調用時,按照正常的事務傳播行為規則開啟事務
4.未開啟注解解析: 配置文件必須加<tx:annotation-driven />,否則不解析@Transactional
5.異常被try{}catch(){}捕捉到了,有異常就不會回滾。
6. 數據庫引擎要支持事務: 如果是mysql,注意表要使用支持事務的引擎,比如innodb,如果是myisam,事務是不起作用的
項目中問題的最終處理:
由於 Controller 層中異常不能直接拋到用戶,對異常進行了try{}catch(){},導致事務回滾失效,無法通過在 Controller 層添加事務管理來實現事務功能,
所以只能有用以下方式來處理:
1. 將業務邏輯代碼移到 service 來處理 : 推薦方法
2. 如果業務邏輯復雜,在維護的時候無法保證邏輯正確的情況下,只有手動編寫事務代碼來實現回滾了,具體代碼如下:(不推薦)
//在每個controller中注入transactionManager @Resource private PlatformTransactionManager transactionManager; @PostMapping(value = "setCode") @ResponseBody public void setCode(Invoice invoice, InvoiceAddress invoiceAddress,String token,String orderIDs, Integer pid,HttpServletResponse response){ DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = transactionManager.getTransaction(defaultTransactionDefinition); try { invoiceService.insert(token,pid,invoice); int iID= invoice.getId(); String substring = orderIDs.substring(0, orderIDs.length()-1); String[] split = substring.split(","); for (String string2 : split) { bOrderService.updateIStatus("1",string2); } invoiceOrderService.insert(iID,substring); if(Integer.parseInt(invoice.getiType())==1){ invoiceAddressService.insert(iID,invoiceAddress); } System.out.println("======制造一個運行時異常aa======"); System.out.println("運行時異常:"+100/0); //沒有異常便手動提交事務 transactionManager.commit(status); printJson(response,result(200,"ok")); }catch (Exception e){ //有異常便回滾事務 transactionManager.rollback(status); e.printStackTrace(); printJson(response,result(500,"false")); } }