|
前言
今天,發(fā)現(xiàn)了一個(gè)以前寫(xiě)的使用Spring聲明式事務(wù)管理的程序爆出了數(shù)據(jù)庫(kù)連接錯(cuò)誤,感覺(jué)是非常典型的一個(gè)誤用Spring聲明式事務(wù)管理的例子,拿出來(lái)為大家點(diǎn)評(píng)一下。
請(qǐng)先閱讀我之前寫(xiě)的關(guān)于事務(wù)管理的文章:《事務(wù)管理最佳實(shí)踐全面解析》, 《事務(wù)管理最佳實(shí)踐多余的話之一“每次請(qǐng)求,一次數(shù)據(jù)庫(kù)連接,一次事務(wù)”是不是金科玉律?》, 《事務(wù)管理最佳實(shí)踐多余的話之二:Transaction后綴給聲明式事務(wù)管理帶來(lái)的好處》
Spring聲明式事務(wù)管理出錯(cuò)示例
這個(gè)應(yīng)用程序是使用Spring管理的iBatis程序。事務(wù)使用了Spring的聲明式事務(wù)管理。
爆出了如下的錯(cuò)誤:
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)
可以看出,出現(xiàn)的錯(cuò)誤是,Spring管理下的iBatis使用的數(shù)據(jù)庫(kù)連接是只讀的。而在DAO類(lèi)的方法中,卻使用了修改數(shù)據(jù)庫(kù)的iBatis方法。
源代碼:
WcmsSystemNewsinfoDao類(lèi)的源代碼就不列出來(lái)了。這個(gè)Dao類(lèi)的add方法使用了iBatis的update方法,更新數(shù)據(jù)庫(kù)數(shù)據(jù)。
下面是WcmsSystemNewsinfoService類(lèi)的部分相關(guān)調(diào)用方法的源代碼:
public String formatRawInfoToHtml(String infoRawId) throws Exception{
WcmsSystemNewsinfoRawModule wcmsSystemNewsinfoRawModule=new WcmsSystemNewsinfoRawModule();
wcmsSystemNewsinfoRawModule.setInfoId(infoRawId);
//原表數(shù)據(jù)
wcmsSystemNewsinfoRawModule=this.getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule=new WcmsSystemNewsinfoModule();
//目標(biāo)表數(shù)據(jù)
BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
wcmsSystemNewsinfoModule.setInfoId(null);
returnthis.saveOrUpdate(wcmsSystemNewsinfoModule);
}
/**
*需要把目標(biāo)表Model信息保存在目標(biāo)表中。如果該記錄已存在,則更新,否則,新增!
*
*@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;
}
源代碼說(shuō)明:
WcmsSystemNewsinfoService類(lèi)的formatRawInfoToHtml(String infoRawId)方法,根據(jù)傳入的參數(shù),準(zhǔn)備好所需的數(shù)據(jù),然后調(diào)用本類(lèi)的saveOrUpdate(WcmsSystemNewsinfoModule module)方法。
saveOrUpdate()方法,根據(jù)情況,調(diào)用WcmsSystemNewsinfoDao類(lèi)的add或者update方法。
Spring事務(wù)配置文件
一、事務(wù)配置抽象Bean聲明
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">readOnly</prop>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="modify*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="remove*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="query*">PROPAGATION_REQUIRED, readOnly,-Exception</prop>
<prop key="load*">PROPAGATION_REQUIRED, -Exception</prop>
</props>
</property>
</bean>
二、服務(wù)類(lèi)的Spring聲明式事務(wù)管理
<!--
wcmsSystemNewsinfoService
-->
<bean id="wcmsSystemNewsinfoService"
parent="txProxyTemplate">
<property name="target">
<ref bean="wcmsSystemNewsinfoServiceTarget"/>
</property>
</bean>
錯(cuò)誤解析
錯(cuò)誤的原因就在上面的Spring聲明式事務(wù)里。執(zhí)行WcmsSystemNewsinfoService類(lèi)的formatRawInfoToHtml()方法時(shí),會(huì)應(yīng)用txProxyTemplate的配置,由于它的方法名與所有的特殊配置都不匹配,因此,會(huì)應(yīng)用第一個(gè)聲明式事務(wù):
<prop key="*">readOnly</prop>
因此,SpringAOP創(chuàng)建了一個(gè)只讀的數(shù)據(jù)庫(kù)連接和事務(wù)。
然后,調(diào)用WcmsSystemNewsinfoService類(lèi)的saveOrUpdate()方法,也會(huì)查找上面的事務(wù)配置,匹配:
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
SpringAOP會(huì)去得到數(shù)據(jù)庫(kù)連接和設(shè)置事務(wù)。由于在本地線程變量中已經(jīng)找到了前面提供的只讀連接,就會(huì)直接使用這個(gè)數(shù)據(jù)庫(kù)連接,并在其上設(shè)置事務(wù)。
最后,調(diào)用WcmsSystemNewsinfoDao類(lèi)的add方法。由于使用的是只讀連接,執(zhí)行add方法中的update語(yǔ)句,就發(fā)生了上面的錯(cuò)誤:
Caused by:
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
Spring真的能夠管理一切嗎?
像上面這樣的Spring事務(wù)配置和Service、Dao的寫(xiě)法,應(yīng)當(dāng)說(shuō)是相當(dāng)普遍的,我們認(rèn)為Spring已經(jīng)把數(shù)據(jù)庫(kù)連接、事務(wù)、O-R映射等等管得妥妥當(dāng)當(dāng)了,不用我們?cè)俨傩牧?,再理解事?wù)了!
真的如此嗎?錯(cuò)!
漠視數(shù)據(jù)庫(kù)、漠視事務(wù),我們的系統(tǒng)到底有多少類(lèi)似的隱患?我們的業(yè)務(wù)服務(wù)類(lèi)Service浪費(fèi)了多少SPringAOP的幫助?降低了多少性能?
Spring、EJB這樣的聲明式事務(wù),確實(shí)大大方便了我們處理數(shù)據(jù)庫(kù)連接和事務(wù)。但是,我們還是需要自己理解業(yè)務(wù)邏輯對(duì)數(shù)據(jù)庫(kù)連接,對(duì)事務(wù)的需要!
工具只能幫助我們解決我們認(rèn)識(shí)到的問(wèn)題,解決不了我們都沒(méi)理解的問(wèn)題。
不能再把一切扔給框架、容器、工具!首先理解你的業(yè)務(wù)邏輯,理解你要實(shí)現(xiàn)的功能,然后搞清楚框架、容器、工具會(huì)幫助我們做什么。只有理解了自己的業(yè)務(wù)邏輯,理解了自己的代碼,理解了自己要用到的第三方代碼,才能真正完美地實(shí)現(xiàn)我們需要的功能!
用我們的命名方法來(lái)重構(gòu)上面的問(wèn)題代碼
一、給Service類(lèi)中的2個(gè)方法加上后綴名標(biāo)識(shí)對(duì)事務(wù)的依賴(lài)
當(dāng)然,Service接口相應(yīng)的方法也要改變。
/* (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);
//原表數(shù)據(jù)
wcmsSystemNewsinfoRawModule=this.getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule=new WcmsSystemNewsinfoModule();
//目標(biāo)表數(shù)據(jù)
BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
wcmsSystemNewsinfoModule.setInfoId(null);
returnthis.saveOrUpdateDao(wcmsSystemNewsinfoModule);
}
/**
*需要把目標(biāo)表Model信息保存在目標(biāo)表中。如果該記錄已存在,則更新,否則,新增!
*
*@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方法可以直接被最終用戶(hù)調(diào)用。它將能夠創(chuàng)建或得到數(shù)據(jù)庫(kù)連接,管理事務(wù),最后關(guān)閉數(shù)據(jù)庫(kù)連接。
而,saveOrUpdateDao方法,不能直接被最終用戶(hù)調(diào)用。如果它需要數(shù)據(jù)庫(kù)連接,它可以使用本地線程變量中保存的數(shù)據(jù)庫(kù)連接。
二、修改Service類(lèi)的聲明式事務(wù)的配置文件
<!--
wcmsSystemNewsinfoService
-->
<bean id="wcmsSystemNewsinfoService"
parent="txProxyTemplate">
<property name="target">
<ref bean="wcmsSystemNewsinfoServiceTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*Transaction">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
這里,重載了txProxyTemplate的聲明式事務(wù)配置。我們只對(duì)Service類(lèi)中的以Transaction結(jié)尾的方法,應(yīng)用事務(wù),在發(fā)生異常時(shí),回滾。
這樣,Transaction方法直接或者間接調(diào)用的DAO接口中的方法,就可以使用本地線程變量中保存的由Transaction方法的AOP代理方法創(chuàng)建的數(shù)據(jù)庫(kù)連接。
數(shù)據(jù)庫(kù)連接和事務(wù)被真正的擺平了!世界真美好!
|
|
|
來(lái)自: WindySky > 《事務(wù)管理》