记一次SpringMVC事务失效
背景
前段时间用Spring+SpringMVC框架开发公司某个系统,测试的时候,发现操作明显失败了,数据也没回滚,然后开始了漫长的排查。
解决过程
- 事务相关包导入正确。
- 检查事务相关配置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>
- 检查需要事务的地方是否添加了注解 @Transactional(rollbackFor = Exception.class)(项目中已添加,没问题)。
- 检查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>
- 检查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>
- 检查了一系列的配置之后,暂时都没有发现任何问题,可是再次尝试之后事务仍然没有生效,令人很费解。
- 检查注解了事务的方法是否被未加事务的同一个服务的方法调用(这种情况下事务不会生效),检查代码之后未发现异常。
- 检查注解了事务的方法有没有不当的try catch并且没有手动回滚(这种情况下事务也不会生效),检查代码之后仍未发现异常
- 经过一番折腾后,决定写个测试类在本地调试一下。(分别测试了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; } }
- 这两种方法事务都生效了,为什么启动整个项目后就不生效了!!!看来一定是环境的问题。
- 对比不同,发现使用测试类时,容器中仅仅加载了spring.xml的配置,而整个项目启动时,不仅会加载spring.xml的配置,还会加载spring-mvc.xml的配置。最终,在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