1.spring
Transaction rolled back because it has been marked as rollback-only
事务已回滚,由于它被标记成了只回滚
<prop key="query*">PROPAGATION_REQUIRED,readOnly</prop>
query开头的方法readOnly,因此只能select,抛出异常,insert/update/delete操做必然回滚app
2.测试
发现selectA调用selectB,若是selectB抛出Exception,selectA中捕获Exception可是并不继续向外抛出,最后会出现错误。this
纠其原理其实很简单,在selectB返回的时候,transaction被设置为rollback-only了,可是selectA正常消化掉,没有继续向外抛。
那么selectA结束的时候,transaction会执commit操做,可是 transaction已经被设置为 rollback-only了。
因此会出现这个错误。
有的同窗说了,那不是没得搞了,service不能抛出异常,或者不能拦截异常了?
其实否则,其实错误不在这里,而是select这种操做为何要启动事务呢?spa
3.demo示例代码code
1.applicationContext.xml配置事务xml
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- <tx:method name="sendIllegalMessage" read-only="false" rollback-for="Exception" propagation="REQUIRES_NEW" /> --> <tx:method name="get*" read-only="true" /> <tx:method name="find*" read-only="true" /> <tx:method name="load*" read-only="true" /> <tx:method name="query*" read-only="true" /> <tx:method name="add*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="batchAdd*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="save*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="insert*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="update*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="modify*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="delete*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="del*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="registe*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="approve*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="clear*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="set*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="reset*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="getUpdate*" read-only="false" rollback-for="Exception" propagation="REQUIRED" /> <tx:method name="updatedQuery*" read-only="false" rollback-for="Exception" propagation="REQUIRES_NEW" /> <!-- <tx:method name="*" read-only="true"/> --> </tx:attributes> </tx:advice>
<aop:config> <aop:advisor pointcut="execution(* com.xxx.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v30.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v31.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v33.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v34.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.limitCoupon.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v35.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.v36.service..*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.auth.*Service.*(..))" advice-ref="txAdvice"/> <aop:advisor pointcut="execution(* com.xxx.notify.*Service.*(..))" advice-ref="txAdvice"/> </aop:config>
2.junit测试代码blog
@Test public void testCancelTask2(){ try { transService.updateTransCancel2(); } catch (Exception e) { e.printStackTrace(); } }
public void updateTransCancel2() { int upCount = transMapper.updateTransCancelStat(520657512071l, 1, 0, 0,-1,-1,-1,-1,-1,-1); try { cancelTransSendSms.cancelTransSendSms2(); } catch (Exception e) { e.printStackTrace(); } logger.info("upCount="+upCount); } public void cancelTransSendSms2() throws Exception{ aotoCancel2(); } private void aotoCancel2() { txtMap=smsConverUtil.getMessage(smsParamsMap, "RenterNoAuthDeposite", "RenterNoAuthDeposite0000"); } public Map<String,String> getMessage(Map<String,Object> smsParamsMap,String smsContentKey,String pushKey){ Map<String,String> map=new LinkedHashMap<String, String>(); String smsContent=""; String jpushContent=""; String smsMessage=""; String flag=""; logger.info("in rentNo->smsContentKey is {}",smsContentKey); if(StringUtils.isNotBlank(smsContentKey)){ smsContent=getContent(smsParamsMap,smsContentKey); smsMessage=smsMsgDescMap.get(smsContentKey); } if(StringUtils.isNotBlank(pushKey)){ jpushContent=getPushContentTemplate(pushKey,smsParamsMap); flag=pushMsgFlagMap.get(pushKey); } map.put("smsContent",smsContent); map.put("jpushContent",jpushContent); map.put("smsMessage",smsMessage); map.put("flag",flag); return map; } private String getPushContentTemplate(String contentKey,Map<String,Object> contentParamMap){ try { String templateContent = operationService.getTemplateMsgByAppTypeAndCode(AppTypeConstant.JPUSH, contentKey); if(StringUtils.isEmpty(templateContent)){ return null; } return replaceTemplateContent(templateContent,contentParamMap); } catch (Exception e) { logger.error("推送消息获取消息内容报错!",e); } return null; }
public String getTemplateMsgByAppTypeAndCode(String appType, String textCode) { try { return operationTextCache.getUpdateOperateTextMsgByAppTypeAndTextCode(appType, textCode); } catch (Exception e) { e.printStackTrace(); } return null; } public String tgetTemplateMsgByAppTypeAndCode(String appType, String textCode) { return operationTextCache.tfindOperateTextMsgByAppTypeAndTextCode(appType, textCode); }
public String findOperateTextMsgByAppTypeAndTextCode(String appType, String textCode) { OperationText operationText = this.findOperationTextByAppTypeAndTextCode(appType, textCode); if (operationText==null) { throw new IllegalArgumentException("appType:"+appType+","+"textCode:"+textCode+",不存在文本模板消息"); }else { return operationText.getTextMsg(); } } public String getUpdateOperateTextMsgByAppTypeAndTextCode(String appType, String textCode) { OperationText operationText = this.findOperationTextByAppTypeAndTextCode(appType, textCode); if (operationText==null) { throw new IllegalArgumentException("appType:"+appType+","+"textCode:"+textCode+",不存在文本模板消息"); }else { return operationText.getTextMsg(); } } public String tfindOperateTextMsgByAppTypeAndTextCode(String appType, String textCode) { OperationText operationText = this.findOperationTextByAppTypeAndTextCode(appType, textCode); if (operationText==null) { throw new IllegalArgumentException("appType:"+appType+","+"textCode:"+textCode+",不存在文本模板消息"); }else { return operationText.getTextMsg(); } }
4.汇总(A调用B)事务
4.1 A无事务,B无事务(将find,get改为tfind,tget方法名) A不回滚,不报以上错误。get
4.2 A无事务,B get,find只读事务,可是不抛出throw new IllegalArgumentException("appType:"+appType+","+"textCode:"+textCode+",不存在文本模板消息"); A不回滚,不报以上错误。
4.3 A update事务,B get,find只读事务且抛出异常 (间隔捕获) A回滚,报以上错误。
4.4 A无事务,B get,find只读事务且抛出异常 (间隔捕获) A回滚,报以上错误。
4.5 A update事务,B update事务且抛出异常 (间隔捕获) A回滚,报以上错误。
4.6 A update事务,B update事务且抛出异常且try..catch..B A不回滚,不报以上错误。
4.6 A无事务,B update事务且抛出异常且try..catch..B A不回滚,不报以上错误。
简单而言之:
方法1有try,方法2无try,方法3 find或get throws A回滚,报以上错误。 捕获的异常有间隔有问题。
方法1有try,方法2有try,方法3 find或get throws A不回滚,不报以上错误。 在抛出异常的上一级方法捕获没有问题。
基于以上的状况说明:类1方法1无事务,类2方法2有事务get/find无捕获,类3方法3无事务 --->报rollback-only错误。
基于以上的状况说明:类1方法1无事务,类2方法2有事务get/find有捕获,类3方法3无事务 --->不报rollback-only错误。 上文说的间隔try
类1方法1无事务,类2方法2有事务get/find有无捕获,类3方法3有事务 --->报rollback-only错误。 被spring标记了rollback位,这就是为何要REQUIRES_NEW事务了。
类1方法1无事务,类2方法2有事务updatedQuery新建事务有捕获,类3方法3有事务 --->不报rollback-only错误。
类1方法1无事务,类2方法2有事务updatedQuery新建事务无捕获,类3方法3有事务 --->报rollback-only错误。