一、問題産生背景
應用上線的時候,正常調用Tomcat的shutdown.sh腳本,事務執行一半異常送出。僞代碼如下:
@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沒有。
二、思路解析
1、直接将Tomcat服務kill掉能否重制問題
按之前的了解是,Tomcat重新開機事務中斷,資料庫在事務連接配接逾時後會復原事務。那麼寫一段代碼試一下,使用Kill -9指令中斷tomcat服務後發現資料庫事務竟然復原了。
2、分析Tomcat的shutdown.sh指令
其實shutdown.sh指令最終調用的是catalina.sh指令腳本,看sh源碼如下:
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這個類來關閉服務的,我們再來看這個類的内容。
if (server instanceof Lifecycle) {
try {
((Lifecycle) server).stop();
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
Tomcat是将注冊進來的服務循環逐個關閉,這時候在關閉的時候可能會因為前一個資源關閉而造成後一個資源抛出異常,注意這個異常有可能是Throwable,也可能是Exception,後面詳細解釋。
3、分析Spring注解事務源碼
Paste_Image.png
在Tomcat關閉的時候,抛出的異常和上面代碼的異常沒有比對成功,spring異常比對采用疊代目前異常的所有父類與目标異常比對,比對不到後檢查目前異常是否為Error或者RuntimeException的執行個體,是的話也能比對上,但是沒有比對是否為Throwable的執行個體
三、問題總結
通過上面的問題分析,可以把代碼寫成如下樣式:
@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異常重新開機,事務能夠正确復原。