欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

记一次SpringMVC事务失效

程序员文章站 2022-03-15 21:02:21
背景前段时间用Spring+SpringMVC框架开发公司某个系统,测试的时候,发现操作明显失败了数据也没有回滚,然后开始了漫长的排查。解决过程事务相关包导入正确。检查事务相关配置spring-mybatis.xml(仔细对比后发现这里的配置没有任何问题)。

背景

前段时间用Spring+SpringMVC框架开发公司某个系统,测试的时候,发现操作明显失败了,数据也没回滚,然后开始了漫长的排查。
 


解决过程

  1. 事务相关包导入正确。
  2. 检查事务相关配置spring-mybatis.xml(仔细对比后发现这里的配置没有任何问题)。
    	<?xml version="1.0" encoding="UTF-8"?>
    	<beans xmlns="http://www.springframework.org/schema/beans"
    	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	       xmlns:tx="http://www.springframework.org/schema/tx"
    	       xmlns:context="http://www.springframework.org/schema/context"
    	       xsi:schemaLocation="http://www.springframework.org/schema/beans
    	       http://www.springframework.org/schema/beans/spring-beans.xsd
    	       http://www.springframework.org/schema/tx
    	       http://www.springframework.org/schema/tx/spring-tx.xsd
    	       http://www.springframework.org/schema/context
    	       http://www.springframework.org/schema/context/spring-context.xsd">
    	
    	    <context:component-scan base-package="com.example.project.dao"/>
    	
    	    <!-- dataSource -->
    	    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    	        <property name="propertiesLocation" value="classpath:jdbc.properties"/>
    	    </bean>
    	
    	    <!--mybatis -->
    	    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	        <property name="dataSource" ref="dataSource"/>
    	        <property name="configLocation" value="classpath:sqlmap-config.xml"/>
    	        <property name="typeAliasesPackage" value="com.jdd.manualaudit.dao.entity"/>
    	        <property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
    	        <property name="transactionFactory">
    	            <bean class="org.mybatis.spring.transaction.SpringManagedTransactionFactory"/>
    	        </property>
    	    </bean>
    	
    	    <!--配置Mybatis模版 -->
    	    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    	        <constructor-arg index="0" ref="sqlSessionFactory"/>
    	        <!-- 执行方式,SIMPLE, REUSE, BATCH
    	        <constructor-arg index="1" value="BATCH" />-->
    	    </bean>
    	      
    	    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	        <property name="basePackage" value="com.example.project.dao.mapper"/>
    	    </bean>
    	
    	    <!--事务管理器 -->
    	    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	        <property name="dataSource" ref="dataSource"/>
    	    </bean>
    	     <!--开启事务注解,开启CGlib代理 -->
    	    <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
    	
    </beans>
  3. 检查需要事务的地方是否添加了注解 @Transactional(rollbackFor = Exception.class)(项目中已添加,没问题)。
  4. 检查Spring的配置spring.xml。(这里的功能无非就是扫描包和导入配置即spring-mybatis.xml和系统classpath下的.proprtties文件,多次检查后也没发现任何问题)。
    	<?xml version="1.0" encoding="UTF-8"?>
    	<beans xmlns="http://www.springframework.org/schema/beans"
    	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	       xmlns:context="http://www.springframework.org/schema/context"
    	       xsi:schemaLocation="http://www.springframework.org/schema/beans
    	    http://www.springframework.org/schema/beans/spring-beans.xsd
    	    http://www.springframework.org/schema/context
    	    http://www.springframework.org/schema/context/spring-context.xsd">
    	
    	    <context:property-placeholder location="classpath*:*.properties" file-encoding="UTF-8"/>
    	    <context:component-scan base-package="com.example.project">
    	    <context:annotation-config/>
    	    <import resource="spring-mybatis.xml"/>
    	
            </beans>
  5. 检查SpringMVC的配置spring-mvc.xml。(这里的功能无非也是配置了一系列的解析器、转换器、扫描包和导入系统classpath下的.proprtties文件,看上去貌似也没什么问题)。
    	<?xml version="1.0" encoding="UTF-8"?>
    	<beans xmlns="http://www.springframework.org/schema/beans"
    	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	       xmlns:context="http://www.springframework.org/schema/context"
    	       xmlns:mvc="http://www.springframework.org/schema/mvc"
    	       xmlns:p="http://www.springframework.org/schema/p"
    	       xsi:schemaLocation="http://www.springframework.org/schema/beans
    	                            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    	                            http://www.springframework.org/schema/mvc
    	                            http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
    	                            http://www.springframework.org/schema/context
    	                            http://www.springframework.org/schema/context/spring-context-3.0.xsd"
    	       default-lazy-init="true" default-autowire="byName">
    	
    	    <context:property-placeholder location="classpath*:*.properties"/>
    	    <!--对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 -->
    	    <context:component-scan base-package="com.example.project"/>
    	
    	    <!-- 启用springMVC的默认配置-->
    	    <mvc:annotation-driven/>
    	    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter ">
    	        <property name="messageConverters">
    	            <list>
    	                <!--json转换-->
    	                <ref bean="mappingJacksonHttpMessageConverter"/>
    	            </list>
    	        </property>
    	    </bean>
    	
    	    <!-- 文件上传解析器 -->
    	    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    	        <property name="defaultEncoding" value="UTF-8"/>
    	        <property name="maxUploadSize" value="5242880"/>
    	    </bean>
    	
    	    <!-- spring文件下载 -->
    	    <bean id="arrayHttpMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
    	
    	    <!-- 解决IE低版本下载返回json数据 -->
    	    <bean id="mappingJacksonHttpMessageConverter"
    	          class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    	        <property name="supportedMediaTypes">
    	            <list>
    	                <value>text/html;charset=utf-8</value>
    	                <value>application/json;charset=UTF-8</value>
    	            </list>
    	        </property>
    	    </bean>
    	
    	    <!-- 解决中文乱码:该类解决当返回的数据是字符串包含中文时出现乱码问题 -->
    	    <bean id="stringHttpMessageConverter"
    	          class="org.springframework.http.converter.StringHttpMessageConverter">
    	        <property name="supportedMediaTypes">
    	            <list>
    	                <value>text/html;charset=utf-8</value>
    	            </list>
    	        </property>
    	    </bean>
    	
    </beans>
  6. 检查了一系列的配置之后,暂时都没有发现任何问题,可是再次尝试之后事务仍然没有生效,令人很费解。
  7. 检查注解了事务的方法是否被未加事务的同一个服务的方法调用(这种情况下事务不会生效),检查代码之后未发现异常。
  8. 检查注解了事务的方法有没有不当的try catch并且没有手动回滚(这种情况下事务也不会生效),检查代码之后仍未发现异常
  9. 经过一番折腾后,决定写个测试类在本地调试一下。(分别测试了Mapper和Service)
    ​
    	package base;
    
    	import org.junit.runner.RunWith;
    	import org.springframework.test.context.ContextConfiguration;
    	import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    	import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    	import com.example.project.dao.TestMapper 
    	//测试1:直接用mapper(事务生效了)
    	@RunWith(SpringJUnit4ClassRunner.class)
    	@ContextConfiguration(locations = { "/config/spring.xml"})
    	public class BaseTestCase extends AbstractJUnit4SpringContextTests {
    	@Resource
    	private TestMapper testMapper;
    	
    	@Test
    	@Transactional(rollbackFor = Exception.class)
    	    public void test() throws ParseException {
    	            Test test=new Test();
    	          test.setName("测试");
    	             testMapper.insert(test)
    	          int i=1/0;
    	      }
    	}
    
    ​
    ​
    	package base;
    
    	import org.junit.runner.RunWith;
    	import org.springframework.test.context.ContextConfiguration;
    	import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    	import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    	import com.example.project.service.TestService 
    	//测试2:用service,服务中相关方法添加了事务注解(事务生效了)
    	@RunWith(SpringJUnit4ClassRunner.class)
    	@ContextConfiguration(locations = { "/config/spring.xml"})
    	public class BaseTestCase extends AbstractJUnit4SpringContextTests {
    	@Resource
    	private TestService testService ;
    	
    	@Test
    	@Transactional(rollbackFor = Exception.class)
    	    public void test() throws ParseException {
    	            Test test=new Test();
    	          test.setName("测试");
    	             testService .insert(test)
    	          int i=1/0;
    	      }
    	}
    
    ​
  10. 这两种方法事务都生效了,为什么启动整个项目后就不生效了!!!看来一定是环境的问题。
  11. 对比不同,发现使用测试类时,容器中仅仅加载了spring.xml的配置,而整个项目启动时,不仅会加载spring.xml的配置,还会加载spring-mvc.xml的配置。最终,在SpringMVC的官方文档发现了下面这幅图:

    记一次SpringMVC事务失效

     

    图的描述

        这幅图描述的是SpringMVC与Spring的上下文层次关系,下层的Root WebApplicationContext 是spring初始化的容器,上层的Servlet WebApplicationContext 则是SpringMVC的初始化容器,Spring容器会先初始化,并且从概念上说是SpringMVC容器的父容器。从delegates if no bean found可以知道,当SpringMVC容器中没有找到需要用的bean时,就会去父容器中加载。

官方文档描述

        Root WebApplicationContext通常包含基础的bean,例如需要在多个Servlet实例之间共享的数据存储库和业务中的服务。这些Bean被有效地继承,并且可以在Servlet特定的子级中重写(即重新声明),Servlet WebApplicationContext通常包含给定本地的Bean Servlet。也就是说,在一定的情况下,SpringMVC会覆盖掉spring容器中的同名bean。

最终原因

       检查spring.xml和spring-mvc.xml,发现两个配置文件中扫描的路径都为<context:component-scan base-package="com.example.project"/>而该路径下包含了系统的所有组件(包括mapper、service、controller),spring容器首先会初始化,接着,在SpringMVC容器初始化的过程中遇到与父容器中同名的bean会直接覆盖掉。因为spring-mvc.xml扫描了系统所有的组件,初始化完成后,容器中的bean全都是SpringMVC创建的bean。而子容器创建的Service是没有事务性的(不会对类进行增强),因此事务失效了。

解决办法

spring-mvc.xml只扫描controller,最终的spring-mvc.xml

​
	<?xml version="1.0" encoding="UTF-8"?>
	<beans xmlns="http://www.springframework.org/schema/beans"
	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	       xmlns:context="http://www.springframework.org/schema/context"
	       xmlns:mvc="http://www.springframework.org/schema/mvc"
	       xmlns:p="http://www.springframework.org/schema/p"
	       xsi:schemaLocation="http://www.springframework.org/schema/beans
	                            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	                            http://www.springframework.org/schema/mvc
	                            http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
	                            http://www.springframework.org/schema/context
	                            http://www.springframework.org/schema/context/spring-context-3.0.xsd"
	       default-lazy-init="true" default-autowire="byName">
	
	    <context:property-placeholder location="classpath*:*.properties"/>
	    <!--只扫描controller包,以完成Bean创建和自动依赖注入的功能 -->
	    <context:component-scan base-package="com.example.project.controller"/>
	
	    <!-- 启用springMVC的默认配置-->
	    <mvc:annotation-driven/>
	    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter ">
	        <property name="messageConverters">
	            <list>
	                <!--json转换-->
	                <ref bean="mappingJacksonHttpMessageConverter"/>
	            </list>
	        </property>
	    </bean>
	
	    <!-- 文件上传解析器 -->
	    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	        <property name="defaultEncoding" value="UTF-8"/>
	        <property name="maxUploadSize" value="5242880"/>
	    </bean>
	
	    <!-- spring文件下载 -->
	    <bean id="arrayHttpMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
	
	    <!-- 解决IE低版本下载返回json数据 -->
	    <bean id="mappingJacksonHttpMessageConverter"
	          class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
	        <property name="supportedMediaTypes">
	            <list>
	                <value>text/html;charset=utf-8</value>
	                <value>application/json;charset=UTF-8</value>
	            </list>
	        </property>
	    </bean>
	
	    <!-- 解决中文乱码:该类解决当返回的数据是字符串包含中文时出现乱码问题 -->
	    <bean id="stringHttpMessageConverter"
	          class="org.springframework.http.converter.StringHttpMessageConverter">
	        <property name="supportedMediaTypes">
	            <list>
	                <value>text/html;charset=utf-8</value>
	            </list>
	        </property>
	    </bean>
	
</beans>

​

 


总结

总结:SpringMVC初始化过程中覆盖掉了具有事务性的Spring容器中的bean。

 

本文地址:https://blog.csdn.net/qq_33230747/article/details/110235315

相关标签: Spring java