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錯誤。