1、問題產生背景java
應用上線的時候,正常調用Tomcat的shutdown.sh腳本,事務執行一半異常提交。僞代碼以下:spring
@Override @Transactional(propagation = Propagation.REQUIRED) public void insert(PaymentOrder paymentOrder) { try{ paymentOrderDao.update(paymentOrder); PaymentOrderDao.insert(paymentOrder) }catch(Exception e){ logger.error(" 操做支付訂單失敗 biz " + paymentOrder.getBiz() + " bizOrder " + paymentOrder.getBizOrder(), e); Throw e; } }
上面是一段僞代碼,實際在tomcat重啓的時候,上面update語句提交,而insert沒有。數據庫
2、思路解析apache
一、直接將Tomcat服務kill掉可否重現問題
按以前的理解是,Tomcat重啓事務中斷,數據庫在事務鏈接超時後會回滾事務。那麼寫一段代碼試一下,使用Kill -9命令中斷tomcat服務後發現數據庫事務居然回滾了。tomcat
二、分析Tomcat的shutdown.sh命令
其實shutdown.sh命令最終調用的是catalina.sh命令腳本,看sh源碼以下:ide
exec "$_RUNJAVA" $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="$JAVA_ENDORSED_DIRS" -classpath "$CLASSPATH" \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ org.apache.catalina.startup.Bootstrap "$@" stop
其實最終是調用的Bootstrap這個類來關閉服務的,咱們再來看這個類的內容。spa
if (server instanceof Lifecycle) { try { ((Lifecycle) server).stop(); } catch (LifecycleException e) { log.error("Catalina.stop", e); } }
Tomcat是將註冊進來的服務循環逐個關閉,這時候在關閉的時候可能會由於前一個資源關閉而形成後一個資源拋出異常,注意這個異常有多是Throwable,也多是Exception,後面詳細解釋。code
三、分析Spring註解事務源碼server
在Tomcat關閉的時候,拋出的異常和上面代碼的異常沒有匹配成功,spring異常匹配採用迭代當前異常的全部父類與目標異常匹配,匹配不到後檢查當前異常是否爲Error或者RuntimeException的實例,是的話也能匹配上,可是沒有匹配是否爲Throwable的實例圖片
3、問題總結
經過上面的問題分析,能夠把代碼寫成以下樣式:
@Override @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED) public void insert(PaymentOrder paymentOrder) { try{ paymentOrderDao.update(paymentOrder); PaymentOrderDao.insert(paymentOrder) }catch(Throwable e){ logger.error(" 操做支付訂單失敗 biz " + paymentOrder.getBiz() + " bizOrder " + paymentOrder.getBizOrder(), e); Throw e; } }
採用Throwable捕獲方能確保Tomcat異常重啓,事務可以正確回滾。