1. 程式人生 > >Spring MVC @Transactional註解方式事務失效的解決辦法

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

               

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

首先展示問題:

Spring applicationContext.xml配置:

  1. <beanid="dataSource"class="org.springframework.jndi.JndiObjectFactoryBean">
  2.     <propertyname="jndiName">
  3.         <value>java:comp/env/jdbc/will</value>
  4.     </property
    >
  5. </bean>
  6. <beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate">
  7.     <propertyname="dataSource"ref="dataSource"/>
  8. </bean>
  9. <beanid="txManager"
  10.     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  11.     <propertyname="dataSource"ref="dataSource"
    />
  12. </bean>
  13. <!-- 事務控制   -->
  14. <tx:annotation-driventransaction-manager="txManager"/>

Spring mvc.dispatcher.xml配置:

  1. <!-- 自動掃描的包名 -->
  2. <context:component-scanbase-package="com.will">
  3. </context:component-scan>
  4. <!-- 預設的註解對映的支援 -->
  5. <mvc:annotation-driven/>
  6. <!-- 對靜態資原始檔的訪問  -->
  7. <mvc:default-servlet-handler/>
  8. <!-- 攔截器    
  9. <mvc:interceptors>
  10.     <beanclass="com.will.mvc.MyInteceptor"/>
  11. </mvc:interceptors>
  12. -->
  13. <!-- 檢視解釋類 -->
  14. <beanid="viewResolver"
  15.     class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  16.     <propertyname="viewClass"value="org.springframework.web.servlet.view.JstlView"/>
  17.     <propertyname="prefix"value="/WEB-INF/pages/"/>
  18.     <propertyname="suffix"value=".jsp"/>
  19. </bean>
然後在Service層模擬了一個事務回滾的method case:
  1. @Transactional
  2. publicboolean save(Person person)  
  3. {  
  4.    for(int id: newint[]{2,3})  
  5.     {  
  6.         personDao.del(id);  
  7.         int j = 1/0;  
  8.     }                  
  9.     returnfalse;  
  10. }  

本以為大功告成,在執行save方法時,由於1/0 丟擲 java.lang.ArithmeticException: / by zero  RuntimeException,導致事務迴歸。However,no way! So crazy~

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

需要在 applicationContext.xml增加:

  1. <context:component-scanbase-package="com.will">
  2.     <context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
  3. </context:component-scan>
在 Spring mvc.dispatcher.xml增加:
  1. <context:component-scanbase-package="com.will">
  2.     <context:include-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
  3.     <context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Service"/>
  4. </context:component-scan>

由於web.xml中配置:

  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增加:

  1. <context:component-scanbase-package="com.will">
  2. </context:component-scan>
在 Spring mvc.dispatcher.xml增加:
  1. <context:component-scanbase-package="com.will">
  2.     <context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Service"/>
  3. </context:component-scan>

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

  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,對事務處理有點思維定式,這次花費不少時間來解決這個問題。頗為尷尬!