?????????????????????? ??????
事務管理最佳實踐多余的話之三
????????????? Spring
聲明式事務管理出錯示例與解決之道
?
?
?
前言
今天,發現了一個以前寫的使用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類的部分相關調用方法的源代碼:
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聲明
二、服務類的Spring聲明式事務管理
<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>
???????<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>
二、服務類的Spring聲明式事務管理
<!--?
????wcmsSystemNewsinfoService
?????-->
????<bean?id="wcmsSystemNewsinfoService"
???????parent="txProxyTemplate">
???????<property?name="target">
???????????<ref?bean="wcmsSystemNewsinfoServiceTarget"/>
???????</property>
????
????</bean>
????wcmsSystemNewsinfoService
?????-->
????<bean?id="wcmsSystemNewsinfoService"
???????parent="txProxyTemplate">
???????<property?name="target">
???????????<ref?bean="wcmsSystemNewsinfoServiceTarget"/>
???????</property>
????
????</bean>
錯誤解析
錯誤的原因就在上面的Spring聲明式事務里。執行WcmsSystemNewsinfoService類的formatRawInfoToHtml()方法時,會應用txProxyTemplate的配置,由于它的方法名與所有的特殊配置都不匹配,因此,會應用第一個聲明式事務:
??? <prop key="*">readOnly</prop>
因此,SpringAOP創建了一個只讀的數據庫連接和事務。
然后,調用WcmsSystemNewsinfoService類的saveOrUpdate()方法,也會查找上面的事務配置,匹配:
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
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;
????}
?????*?@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
?????-->
????<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>
????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的聲明式事務配置。我們只對Service類中的以Transaction結尾的方法,應用事務,在發生異常時,回滾。
這樣,Transaction方法直接或者間接調用的DAO接口中的方法,就可以使用本地線程變量中保存的由Transaction方法的AOP代理方法創建的數據庫連接。
數據庫連接和事務被真正的擺平了!世界真美好!
?
?
?
?
?Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1423460