Spring MVC @Transactional註解方式事務失效的解決辦法

前文提到,最新換了框架,新項目用SpringMVC + Spring JdbcTemplate。搭框架時,發現了一個事務沒法正常回滾的問題,記錄以下:html

首先展現問題:java

Spring applicationContext.xml配置:mysql

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片web

  1.          

  2. <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">  spring

  3.     <property name="jndiName">  sql

  4.         <value>java:comp/env/jdbc/will</value>  express

  5.     </property>  mvc

  6. </bean>   app

  7.       

  8. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  框架

  9.     <property name="dataSource" ref="dataSource" />  

  10. </bean>  

  11.   

  12. <bean id="txManager"  

  13.     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  

  14.     <property name="dataSource" ref="dataSource" />  

  15. </bean>  

  16.   

  17. <!-- 事務控制   -->  

  18. <tx:annotation-driven transaction-manager="txManager" />  


Spring mvc.dispatcher.xml配置:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <!-- 自動掃描的包名 -->    

  2. <context:component-scan base-package="com.will" >   

  3. </context:component-scan>  

  4.   

  5. <!-- 默認的註解映射的支持 -->  

  6. <mvc:annotation-driven />  

  7.   

  8. <!-- 對靜態資源文件的訪問  -->    

  9. <mvc:default-servlet-handler/>    

  10.     

  11.       

  12. <!-- 攔截器    

  13. <mvc:interceptors>    

  14.     <bean class="com.will.mvc.MyInteceptor" />    

  15. </mvc:interceptors>   

  16. -->   

  17.   

  18. <!-- 視圖解釋類 -->   

  19. <bean id="viewResolver"    

  20.     class="org.springframework.web.servlet.view.UrlBasedViewResolver">    

  21.     <property name="viewClass"  value="org.springframework.web.servlet.view.JstlView" />    

  22.     <property name="prefix" value="/WEB-INF/pages/" />    

  23.     <property name="suffix" value=".jsp" />    

  24. </bean>     


而後在Service層模擬了一個事務回滾的method case:

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. @Transactional  

  2. public boolean save(Person person)  

  3. {  

  4.    for(int id: new int[]{2,3})  

  5.     {  

  6.         personDao.del(id);  

  7.         int j = 1/0;  

  8.     }                  

  9.      

  10.     return false;  

  11. }  


本覺得大功告成,在運行save方法時,因爲1/0 拋出 java.lang.ArithmeticException: / by zero  RuntimeException,致使事務迴歸。However,no way! So crazy~

查了下,發現Spring MVC對於事務配置比較講究,須要額外的配置。解決辦法以下:


須要在 applicationContext.xml增長:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <context:component-scan base-package="com.will">   

  2.     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />   

  3. </context:component-scan>  


在 Spring mvc.dispatcher.xml增長:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <context:component-scan base-package="com.will" >   

  2.     <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />   

  3.     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />    

  4. </context:component-scan>  


因爲web.xml中配置:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <context-param>  

  2.     <param-name>contextConfigLocation</param-name>  

  3.     <param-value>  

  4.              classpath:applicationContext.xml  

  5.     </param-value>  

  6. </context-param>  

  7. <listener>  

  8.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  

  9. </listener>  

  10. <servlet>  

  11.     <servlet-name>dispatcher</servlet-name>  

  12.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  

  13.     <init-param>  

  14.          <param-name>contextConfigLocation</param-name>  

  15.          <param-value>classpath*:/mvc_dispatcher_servlet.xml</param-value>  

  16.     </init-param>  

  17.     <load-on-startup>1</load-on-startup>  

  18. </servlet>  

  19. <servlet-mapping>  

  20.     <servlet-name>dispatcher</servlet-name>  

  21.     <url-pattern>*.do</url-pattern>  

  22. </servlet-mapping>  


Spring容器優先加載由ServletContextListener(對應applicationContext.xml)產生的父容器,而SpringMVC(對應mvc_dispatcher_servlet.xml)產生的是子容器。子容器Controller進行掃描裝配時裝配的@Service註解的實例是沒有通過事務增強處理,即沒有事務處理能力的Service,而父容器進行初始化的Service是保證事務的加強處理能力的。若是不在子容器中將Service exclude掉,此時獲得的將是原樣的無事務處理能力的Service。


( update 2014.05.27  今天看見一種說法:key word =雙親上下文不使用ContextLoaderListener監聽器來加載spring的配置,改用DispatcherServlet來加載spring的配置,不要雙親上下文,只使用一個DispatcherServlet就不會出現上述問題。筆者這裏未測過這個辦法,由於我本身的業務須要一個extends ContextLoaderListener的selfListener,有興趣的朋友能夠本身測試下這個說法,並歡迎把測試的結果與我交流 :) 


通過以上分析,故能夠優化上述配置

在 applicationContext.xml增長:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <context:component-scan base-package="com.will">   

  2. </context:component-scan>  


在 Spring mvc.dispatcher.xml增長:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <context:component-scan base-package="com.will" >      

  2.     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />    

  3. </context:component-scan>  

通過如上配置,能夠發現事務控制部分的日誌以下:

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 2013-09-25 09:53:13,031 [http-8080-2] DEBUG [org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''  

  2. 2013-09-25 09:53:13,037 [http-8080-2] DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Returning cached instance of singleton bean 'txManager'  

  3. 2013-09-25 09:53:13,050 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Creating new transaction with name [com.will.service.impl.PersonServiceImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''  

  4. 2013-09-25 09:53:13,313 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Acquired Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost , MySQL-AB JDBC Driver] for JDBC transaction  

  5. 2013-09-25 09:53:13,323 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Switching JDBC Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost , MySQL-AB JDBC Driver] to manual commit  

  6. 2013-09-25 09:53:13,327 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - Executing prepared SQL update  

  7. 2013-09-25 09:53:13,328 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - Executing prepared SQL statement [delete from person where id=?]  

  8. 2013-09-25 09:53:13,348 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - SQL update affected 1 rows  

  9. 2013-09-25 09:53:13,363 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction rollback  

  10. 2013-09-25 09:53:13,364 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost , MySQL-AB JDBC Driver]  

  11. 2013-09-25 09:53:13,377 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Releasing JDBC Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction  

  12. 2013-09-25 09:53:13,378 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource  


在2013-09-25 09:53:13,363處進行了rollback。


PS:習慣了Structs,對事務處理有點思惟定式,此次花費很多時間來解決這個問題。頗爲尷尬!

相關文章
相關標籤/搜索