Spring宣告式事務管理出錯示例與解決之道

pingyuan發表於2009-01-08
Spring宣告式事務管理出錯示例與解決之道[@more@]
前言
今天,發現了一個以前寫的使用Spring宣告式事務管理的程式爆出了資料庫連線錯誤,感覺是非常典型的一個誤用Spring宣告式事務管理的例子,拿出來為大家點評一下。
請先閱讀我之前寫的關於事務管理的文章:事務管理最佳實踐全面解析事務管理最佳實踐多餘的話之一每次請求,一次資料庫連線,一次事務是不是金科玉律?事務管理最佳實踐多餘的話之二:Transaction字尾給宣告式事務管理帶來的好處
Spring宣告式事務管理出錯示例
這個應用程式是使用Spring管理的iBatis程式。事務使用了Spring的宣告式事務管理。
爆出瞭如下的錯誤:
Caused by:
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at com.withub.wcms.manage.collectnews.systemNewsinfo.dao.WcmsSystemNewsinfoDao.add(WcmsSystemNewsinfoDao.java:50)
at com.withub.wcms.manage.collectnews.systemNewsinfo.service.WcmsSystemNewsinfoService.saveOrUpdate(WcmsSystemNewsinfoService.java:103)
at com.withub.wcms.manage.collectnews.systemNewsinfo.service.WcmsSystemNewsinfoService.formatRawInfoToHtml(WcmsSystemNewsinfoService.java:89)
可以看出,出現的錯誤是,Spring管理下的iBatis使用的資料庫連線是隻讀的。而在DAO類的方法中,卻使用了修改資料庫的iBatis方法。
原始碼:
WcmsSystemNewsinfoDao類的原始碼就不列出來了。這個Dao類的add方法使用了iBatis的update方法,更新資料庫資料。
下面是WcmsSystemNewsinfoService類的部分相關呼叫方法的原始碼:
public String formatRawInfoToHtml(String infoRawId) throws Exception{
WcmsSystemNewsinfoRawModule wcmsSystemNewsinfoRawModule=new WcmsSystemNewsinfoRawModule();
wcmsSystemNewsinfoRawModule.setInfoId(infoRawId);
//原表資料
wcmsSystemNewsinfoRawModule=this.getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule=new WcmsSystemNewsinfoModule();
//目標表資料
BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
wcmsSystemNewsinfoModule.setInfoId(null);
returnthis.saveOrUpdate(wcmsSystemNewsinfoModule);
}
/**
*需要把目標表Model資訊儲存在目標表中。如果該記錄已存在,則更新,否則,新增!
*
*@parammodule
*@throwsException
*/
public String saveOrUpdate(WcmsSystemNewsinfoModule module) throws Exception{
WcmsSystemNewsinfoModule loadModule=this.getWcmsSystemNewsinfoDao().selectWcmsSystemNewsinfoModuleByInfoRawId(module.getInfoRawId());
String infoId=null;
if(loadModule==null){
//插入
infoId=this.getWcmsSystemNewsinfoDao().add(module);
}else{
//更新
/**
*更新
*1,找到主鍵條件
*/
infoId=loadModule.getInfoId();
module.setInfoId(loadModule.getInfoId());
this.getWcmsSystemNewsinfoDao().updateProcessedInfo(module);
}
return infoId;
}
原始碼說明:
WcmsSystemNewsinfoService類的formatRawInfoToHtml(String infoRawId)方法,根據傳入的引數,準備好所需的資料,然後呼叫本類的saveOrUpdate(WcmsSystemNewsinfoModule module)方法。
saveOrUpdate()方法,根據情況,呼叫WcmsSystemNewsinfoDao類的add或者update方法。
Spring事務配置檔案
一、事務配置抽象Bean宣告
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">readOnlyprop>
<prop key="add*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="save*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="modify*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="update*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="remove*">PROPAGATION_REQUIRED,-Exceptionprop>
<prop key="query*">PROPAGATION_REQUIRED, readOnly,-Exceptionprop>
<prop key="load*">PROPAGATION_REQUIRED, -Exceptionprop>
props>
property>
bean>
二、服務類的Spring宣告式事務管理
<!--
wcmsSystemNewsinfoService
--&gt
<bean id="wcmsSystemNewsinfoService"
parent="txProxyTemplate">
<property name="target">
<ref bean="wcmsSystemNewsinfoServiceTarget"/>
property>
bean>
錯誤解析
錯誤的原因就在上面的Spring宣告式事務裡。執行WcmsSystemNewsinfoService類的formatRawInfoToHtml()方法時,會應用txProxyTemplate的配置,由於它的方法名與所有的特殊配置都不匹配,因此,會應用第一個宣告式事務:
<prop key="*">readOnlyprop>
因此,SpringAOP建立了一個只讀的資料庫連線和事務。
然後,呼叫WcmsSystemNewsinfoService類的saveOrUpdate()方法,也會查詢上面的事務配置,匹配:
<prop key="save*">PROPAGATION_REQUIRED,-Exceptionprop>
SpringAOP會去得到資料庫連線和設定事務。由於在本地執行緒變數中已經找到了前面提供的只讀連線,就會直接使用這個資料庫連線,並在其上設定事務。
最後,呼叫WcmsSystemNewsinfoDao類的add方法。由於使用的是隻讀連線,執行add方法中的update語句,就發生了上面的錯誤:
Caused by:
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
Spring真的能夠管理一切嗎?
像上面這樣的Spring事務配置和Service、Dao的寫法,應當說是相當普遍的,我們認為Spring已經把資料庫連線、事務、O-R對映等等管得妥妥當當了,不用我們再操心了,再理解事務了!
真的如此嗎?錯!
漠視資料庫、漠視事務,我們的系統到底有多少類似的隱患?我們的業務服務類Service浪費了多少SPringAOP的幫助?降低了多少效能?
Spring、EJB這樣的宣告式事務,確實大大方便了我們處理資料庫連線和事務。但是,我們還是需要自己理解業務邏輯對資料庫連線,對事務的需要!
工具只能幫助我們解決我們認識到的問題,解決不了我們都沒理解的問題。
不能再把一切扔給框架、容器、工具!首先理解你的業務邏輯,理解你要實現的功能,然後搞清楚框架、容器、工具會幫助我們做什麼。只有理解了自己的業務邏輯,理解了自己的程式碼,理解了自己要用到的第三方程式碼,才能真正完美地實現我們需要的功能!
用我們的命名方法來重構上面的問題程式碼
一、給Service類中的2個方法加上字尾名標識對事務的依賴
當然,Service介面相應的方法也要改變。
/* (non-Javadoc)
* @see com.withub.wcms.manage.collectnews.systemNewsinfo.service.IWcmsSystemNewsinfoService#formatRawInfoToHtml(java.lang.String)
*/
public String formatRawInfoToHtmlTransaction(String infoRawId) throws Exception{
WcmsSystemNewsinfoRawModule wcmsSystemNewsinfoRawModule=new WcmsSystemNewsinfoRawModule();
wcmsSystemNewsinfoRawModule.setInfoId(infoRawId);
//原表資料
wcmsSystemNewsinfoRawModule=this.getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule=new WcmsSystemNewsinfoModule();
//目標表資料
BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
wcmsSystemNewsinfoModule.setInfoId(null);
returnthis.saveOrUpdateDao(wcmsSystemNewsinfoModule);
}
/**
*需要把目標表Model資訊儲存在目標表中。如果該記錄已存在,則更新,否則,新增!
*
*@parammodule
*@throwsException
*/
public String saveOrUpdateDao(WcmsSystemNewsinfoModule module) throws Exception{
WcmsSystemNewsinfoModule loadModule=this.getWcmsSystemNewsinfoDao().selectWcmsSystemNewsinfoModuleByInfoRawId(module.getInfoRawId());
String infoId=null;
if(loadModule==null){
//插入
infoId=this.getWcmsSystemNewsinfoDao().add(module);
}else{
//更新
/**
*更新
*1,找到主鍵條件
*/
infoId=loadModule.getInfoId();
module.setInfoId(loadModule.getInfoId());
this.getWcmsSystemNewsinfoDao().updateProcessedInfo(module);
}
return infoId;
}
這樣,formatRawInfoToHtmlTransaction方法可以直接被終端使用者呼叫。它將能夠建立或得到資料庫連線,管理事務,最後關閉資料庫連線。
而,saveOrUpdateDao方法,不能直接被終端使用者呼叫。如果它需要資料庫連線,它可以使用本地執行緒變數中儲存的資料庫連線。
二、修改Service類的宣告式事務的配置檔案
<!--
wcmsSystemNewsinfoService
--&gt
<bean id="wcmsSystemNewsinfoService"
parent="txProxyTemplate">
<property name="target">
<ref bean="wcmsSystemNewsinfoServiceTarget"/>
property>
<property name="transactionAttributes">
<props>
<prop key="*Transaction">PROPAGATION_REQUIRED,-Exceptionprop>
props>
property>
bean>
這裡,過載了txProxyTemplate的宣告式事務配置。我們只對Service類中的以Transaction結尾的方法,應用事務,在發生異常時,回滾。
這樣,Transaction方法直接或者間接呼叫的DAO介面中的方法,就可以使用本地執行緒變數中儲存的由Transaction方法的AOP代理方法建立的資料庫連線。
資料庫連線和事務被真正的擺平了!世界真美好!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7199667/viewspace-1015875/,如需轉載,請註明出處,否則將追究法律責任。

相關文章