Spring MVC @Transactional注解方式事务失效的解决办法

前文提到,最新换了框架,新项目用SpringMVC + Spring JdbcTemplate。搭框架时,发现了一个事务无法正常回滚的问题,记录如下:

首先展示问题:

Spring applicationContext.xml配置:

        
	<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			<value>java:comp/env/jdbc/will</value>
		</property>
	</bean> 
		
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<!-- 事务控制   -->
	<tx:annotation-driven transaction-manager="txManager" />

Spring mvc.dispatcher.xml配置:

	
    <!-- 自动扫描的包名 -->  
    <context:component-scan base-package="com.will" > 
    </context:component-scan>
	 
    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven />
	
    <!-- 对静态资源文件的访问  -->  
    <mvc:default-servlet-handler/>  
      
        
    <!-- 拦截器  
    <mvc:interceptors>  
    	<bean class="com.will.mvc.MyInteceptor" />  
    </mvc:interceptors> 
    --> 
    
    <!-- 视图解释类 --> 
    <bean id="viewResolver"  
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">  
        <property name="viewClass"  value="org.springframework.web.servlet.view.JstlView" />  
        <property name="prefix" value="/WEB-INF/pages/" />  
        <property name="suffix" value=".jsp" />  
    </bean>  	

然后在Service层模拟了一个事务回滚的method case:

    @Transactional
    public boolean save(Person person)
    {
       for(int id: new int[]{2,3})
        {
            personDao.del(id);
            int j = 1/0;
        }                
       
        return false;
    }

本以为大功告成,在运行save方法时,由于1/0 抛出 java.lang.ArithmeticException: / by zero  RuntimeException,导致事务回归。However,no way! So crazy~

查了下,发现Spring MVC对于事务配置比较讲究,需要额外的配置。解决办法如下:

需要在 applicationContext.xml增加:

<context:component-scan base-package="com.will"> 
  	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
</context:component-scan>

在 Spring mvc.dispatcher.xml增加:

<context:component-scan base-package="com.will" > 
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
</context:component-scan>


由于web.xml中配置:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
             classpath:applicationContext.xml
	</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
	     <param-name>contextConfigLocation</param-name>
	     <param-value>classpath*:/mvc_dispatcher_servlet.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>


Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,而SpringMVC(对应mvc_dispatcher_servlet.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,即没有事务处理能力的Service,而父容器进行初始化的Service是保证事务的增强处理能力的。如果不在子容器中将Service exclude掉,此时得到的将是原样的无事务处理能力的Service, 因为在多上下文的情况下,如果同一个bean被定义两次,后面一个优先


( update 2014.05.27  今天看见一种说法:key word =双亲上下文不使用ContextLoaderListener监听器来加载spring的配置,改用DispatcherServlet来加载spring的配置,不要双亲上下文,只使用一个DispatcherServlet就不会出现上述问题。笔者这里未测过这个办法,因为我自己的业务需要一个extends ContextLoaderListener的selfListener,有兴趣的朋友可以自己测试下这个说法,并欢迎把测试的结果与我交流 :) 

( update 2015.03.20  对于因为双亲上下文引起初始化二次,关键字:springmvc 初始化两次  

经过以上分析,故可以优化上述配置

在 applicationContext.xml增加:

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

在 Spring mvc.dispatcher.xml增加:

<context:component-scan base-package="com.will" >    
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
</context:component-scan>

经过如上配置,可以发现事务控制部分的日志如下:

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; ''
2013-09-25 09:53:13,037 [http-8080-2] DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Returning cached instance of singleton bean 'txManager'
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; ''
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
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
2013-09-25 09:53:13,327 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - Executing prepared SQL update
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=?]
2013-09-25 09:53:13,348 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - SQL update affected 1 rows
2013-09-25 09:53:13,363 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction rollback
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]
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
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,对事务处理有点思维定式,这次花费不少时间来解决这个问题。颇为尴尬!

  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值