最近在做微信項目,我搭建了一個基於servlet,spring3.2,hibernate4.1的框架。因為基於消息的servlet和基於業務層是分開做的,也就是先把業務層做了,再去將所有的請求轉到業務層處理。所以一開始開發就用junit做測試,模擬的消息保存數據庫也都能正常進行。下面列出某一個junit 的 testcase,在這個測試的例子中,我為junit配置了事務,事務也能正常提交。所以,后面的業務層寫法就一直這么開發下去,也沒什么問題
package com.cpic.inf.tools.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import com.alibaba.fastjson.JSONArray; import com.cpic.inf.model.json.JaoFeiJson; import com.cpic.inf.service.SendModelMsgProxy; import com.cpic.inf.tools.exception.SendException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring_*.xml"}) @Transactional @TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)//true:始終回滾 false:數據提交 public class TestCase { @Autowired SendModelMsgProxy proxy; @Test public void testModelMsg() throws SendException{ JaoFeiJson jaofei = new JaoFeiJson(); jaofei.setAccount("62260****4765"); jaofei.setAmount("200元"); jaofei.setContno("張三"); jaofei.setFinish("2014年1月30日"); jaofei.setPaycode("交通銀行"); jaofei.setPeriod("第3期"); jaofei.setPolicyno("201400000009"); jaofei.setProduct("鴻發年年及其附加險"); jaofei.setRemark("如有疑問,請在”更多--小薇在線“里留言~"); jaofei.setTemplate_id("4g3_jAn3qPqhX6DR2TbU1r9-rDN2N4KazEIPLhQ3FKQ"); jaofei.setTitle("您好,您的保單續期交費成功啦~"); //jaofei.setTopcolor("#04B404"); jaofei.setTouser("ox6yJjtSe02C6b3I_Fues2WPYszk"); jaofei.setUrl("http://www.baidu.com"); String str = JSONArray.toJSON(jaofei).toString(); proxy.sendMsg(str); } }
后面當業務層開發完后,將工程放到web容器里跑,發送一個請求,調用到業務層方法,前面一直正常,后面發現一個問題,事務始終不提交,先看我的棕spring部分配置
<!-- 開啟AOP監聽 只對當前配置文件有效 --> <aop:aspectj-autoproxy expose-proxy="true"/> <!-- 開啟注解事務 只對當前配置文件有效 --> <tx:annotation-driven transaction-manager="txManager"/> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="use*" propagation="REQUIRED"/> <!--hibernate4必須配置為開啟事務 否則 getCurrentSession()獲取不到--> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="count*" propagation="REQUIRED" read-only="true" /> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method name="list*" propagation="REQUIRED" read-only="true" /> <tx:method name="send*" propagation="REQUIRED" read-only="true"/> <tx:method name="*" read-only="true" /> </tx:attributes> </tx:advice> <aop:config expose-proxy="true"> <!-- 只對業務邏輯層實施事務 --> <aop:pointcut id="txPointcut" expression="execution(* com.cpic..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> <!-- 自定義前置通知 --> <!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> --> </aop:config> <bean id="methodAdvice" class="com.cpic.inf.service.impl.MethodBefore"></bean>
事務配置是比較清楚的,配置了aspectj的aop做為事務,我想要在所有的impl層的類里加上配置的事務,其中我現在測試的類是com.cpic.inf.service.impl.JaoFeiImpl
來看一下這個類的實現
package com.cpic.inf.service.impl; import java.util.Map; import org.apache.log4j.Logger; import com.alibaba.fastjson.JSONArray; import com.cpic.inf.model.BaseValue; import com.cpic.inf.model.RequestModel; import com.cpic.inf.model.in.JaoFeiModel; import com.cpic.inf.model.mapper.JiaoFeiModelDB; import com.cpic.inf.service.SendModelMsgABS; import com.cpic.inf.tools.Token; import com.cpic.inf.tools.exception.SendException; /** * 交費成功通知 * @author wuxw * */ public class JaoFeiImpl extends SendModelMsgABS { private static Logger logger = Logger.getLogger(Token.class); /** 模版頂部邊框顏色*/ public static final String TOP_COLOR = "#04B404"; /** 模版字體顏色*/ public static final String COLOR = "#000000"; public String sendMsg(Map<String,String> map) throws SendException { String token; try { token = Token.getToken(); } catch (Exception e1) { throw new SendException("無法連接Token服務器"); } RequestModel<JaoFeiModel> reqModel = null; try{ ... try{ this.save(jiaofei); } catch(Exception e){ throw new SendException("數據庫錯誤:" + e.getMessage()); } } catch (Exception e) { throw new SendException("模板參數錯誤!請填寫正確的模板對應參數"); } String json = JSONArray.toJSONString(reqModel); try { logger.info("jiaofei end"); return this.SendModelMsg(json, token); } catch (Exception e) { throw new SendException("網絡異常,無法連接微信服務器"); } } }
按照我的邏輯,如果我調用這個sendMsg方法,就能夠發送模板消息(注:這是一個發送微信模板消息的功能),並將這個模板消息存到數據庫里。另外,用junit測試過,這個功能不存在邏輯錯誤,能夠在junit下,發送消息成功后能正常保存到數據庫里。但后面在web工程里,面前都能成功,直到this.save(jiaofei);的時候,沒有看到hibernate打印出sql(show_sql=true);也就是事務沒提交,為什么會這樣了?
我第一反映是
<aop:pointcut id="txPointcut" expression="execution(* com.cpic..service.*.*(..))" />
這塊是不是配置錯了
在網上查了一下,這個表達式怎么寫,發現沒有寫錯,我如果改成其他目錄的時候
this.save(jiaofei);在執行這句的時候,會報得不到session,也就是表明aop配置是正確的。
因為我的方法是sendMsg方法,事務里沒有配置這個,所以我又在配置里加上了這句
<tx:method name="send*" propagation="REQUIRED" read-only="true"/>
運行的時候還是跟以前的一樣,邏輯上我是沒問題,但一直找不到問題出在哪里,后面在網上找了個投機的辦法
在session.save();的后面加一句session.flush();
這下子,事務能正確提交了,問題暫時是解決了,因為當時數據庫操作只用到了save()方法。接着趕進度。
但做為一個嚴謹的程序,我知道這是不對的,因為我需要的是aop來自動管理事務,而不是現在這樣,操作一次就提交一次,要是遇到事務傳播,這種方法就不奏效了,所以在空閑的時候,我把這個問題仔細查找一下原因。
后面我配置了一個前置消息,配置在aop中,配置是這樣的
<aop:config expose-proxy="true"> <!-- 只對業務邏輯層實施事務 --> <aop:pointcut id="txPointcut" expression="execution(* com.cpic..service.*.*(..))" /> <!-- 自定義前置通知 --> <aop:advisor advice-ref="methodAdvice" pointcut-ref="txPointcut"/> </aop:config> <bean id="methodAdvice" class="com.cpic.inf.service.impl.MethodBefore"></bean>
當我執行到sendMsg()方法之前,是先進入我的前置消息的,也就是證明,不是aop的原因,事務不提交肯定是配置出了問題,我后面在這其他業務類里加個@Transactional注解,事務也提交了,所以我猜,肯定是配置出了問題,我仔細檢查,發現問題出現在
<tx:method name="send*" propagation="REQUIRED" read-only="true"/>
我仔細查了查這里面每個屬性的意思,因為我的理解也都只在單詞字面意思上
propagation="REQUIRED":指事務傳翻
read-only="true":指只讀情況下,不會有事務
所以,我把read-only="true"去掉后,事務就正常了
總結:我以前搭建過很多類似的框架,也就是把幾個框架整合在一起,很多配置也都是從已有的項目中照搬過來,大部分理解也都處理字面意思。所以,今天出現了這樣一個問題,一時半會也解決不了,最終在不服輸的態度下,還是解決了。所以,我的感受是,就像做數學題一樣,還是得多練習,才能在解決問題的速度上上一個層次。希望跟我遇到同樣困難的同學共勉,共同學習!
