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异常重启,事务可以正确回滚。