springmvc 疑点(一) 事物处理失效

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014201191/article/details/50428187
在项目中使用springmvc+spring+mybatis进行项目整合,出现了一个奇怪的现象,首先我来介绍一下项目中的配置,首先是spring-content.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:aop="http://www.springframework.org/schema/aop"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:task="http://www.springframework.org/schema/task"
	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.xsd
       		http://www.springframework.org/schema/aop
			http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
			http://www.springframework.org/schema/tx
        	http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        	http://www.springframework.org/schema/task  
			http://www.springframework.org/schema/task/spring-task-3.1.xsd  
        	">
	<!-- 添加注解驱动 -->
	<context:annotation-config />
	<context:component-scan base-package="com.zefun.web,com.zefun.wechat,com.zefun.app">
	</context:component-scan>
	<task:annotation-driven />
	<context:property-placeholder location="classpath*:properties/dev/*.properties" />
	<import resource="datasource-context.xml" />
	<!-- <import resource="redis-cluster.xml"/> -->
	<import resource="redis-context.xml" />
	<import resource="rabbitmq-context.xml" />
</beans>

下面再给出datasource-context.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:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
  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.xsd
           http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
      http://www.springframework.org/schema/tx
          http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

  <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
    <!-- 慢SQL记录,超过2秒日志输出 -->
    <property name="slowSqlMillis" value="2000" />
      <property name="logSlowSql" value="true" />
  </bean>

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
      <!-- 基本属性 url、user、password -->
      <property name="url" value="${jdbc.url}" />
      <property name="username" value="${jdbc.username}" />
      <property name="password" value="${jdbc.password}" />

      <!-- 配置初始化大小、最小、最大 -->
      <property name="initialSize" value="${jdbc.initialSize}" />
      <property name="minIdle" value="${jdbc.maxIdle}" />
      <property name="maxActive" value="${jdbc.maxActive}" />

      <!-- 配置获取连接等待超时的时间 -->
      <property name="maxWait" value="${jdbc.maxWait}" />

      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
      <property name="timeBetweenEvictionRunsMillis" value="60000" />

      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
      <property name="minEvictableIdleTimeMillis" value="300000" />

      <!-- 测试有效用的SQL Query -->
      <property name="validationQuery" value="SELECT 'x'" />
      <!-- 连接空闲时测试是否有效 -->
      <property name="testWhileIdle" value="true" />
      <!-- 获取连接时测试是否有效 -->
      <property name="testOnBorrow" value="true" />
      <!-- 归还连接时是否测试有效 -->
      <property name="testOnReturn" value="true" />

      <!-- 打开PSCache,并且指定每个连接上PSCache的大小 分库分表较多的数据库,建议配置为false -->
      <property name="poolPreparedStatements" value="false" />
      <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

      <!-- 定期把监控数据输出到日志中 -->
      <!-- <property name="timeBetweenLogStatsMillis" value="300000" /> -->

      <property name="proxyFilters">
        <list>
          <!-- 配置监控统计拦截的filters -->
            <ref bean="stat-filter" />
        </list>
      </property>
  </bean>

  <!-- enable transaction demarcation with annotations -->
    <tx:annotation-driven />
  <!-- PlatformTransactionMnager -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
  </bean>
  <!-- enable transaction annotation support -->
  <tx:annotation-driven transaction-manager="transactionManager" />

  <!-- mybatis分页拦截器 -->
  <bean id="pageInterceptor" class="com.zefun.web.interceptor.PageInterceptor" />

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="plugins">
      <list>
        <ref bean="pageInterceptor" />
      </list>
    </property>
    <property name="mapperLocations" value="classpath*:sqlMap/*.xml"></property>
  </bean>
  <!-- mybatis mapper注入 -->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.zefun.web.mapper" />
  </bean>

</beans>

在给出子容器配置文件:  

<?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.xsd"
  default-lazy-init="true">


  <bean
    class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    p:useCacheControlNoStore="true" p:cacheSeconds="0">
    <property name="messageConverters">
      <list>
        <bean
          class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
      </list>
    </property>
  </bean>

  <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="50000000000000" />
    <property name="maxInMemorySize" value="4096" />
    <property name="defaultEncoding" value="GBK" />
  </bean>

  <bean
    class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
      <list>
        <bean
          class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
        <bean class="com.zefun.common.utils.UTF8StringHttpMessageConverter" />
        <bean class="org.springframework.http.converter.FormHttpMessageConverter" />
        <bean
          class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />
        <bean
          class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
      </list>
    </property>
  </bean>

  <!-- 添加注解驱动 -->
  <context:annotation-config />
  <mvc:annotation-driven />
  <context:component-scan base-package="com.zefun.web,com.zefun.wechat,com.zefun.app">
  </context:component-scan>

  <mvc:default-servlet-handler />

  <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass"
      value="org.springframework.web.servlet.view.JstlView"></property>
    <!-- 配置jsp路径前缀 -->
    <property name="prefix" value="/WEB-INF/view/"></property>
    <!-- 配置URl后缀 -->
    <property name="suffix" value=".jsp"></property>
  </bean>

  <!-- 配置session超时拦截器 -->
  <mvc:interceptors>
    <mvc:interceptor>
      <mvc:mapping path="/**" />
      <bean class="com.zefun.web.interceptor.SessionInterceptor">
        <property name="allowUrlPatterns">
          <list>
            <value>/user/*.*</value>
          </list>
        </property>
      </bean>
    </mvc:interceptor>
    <mvc:interceptor>
      <mvc:mapping path="/**" />
      <bean class="com.zefun.web.interceptor.AuthorityInterceptor">
        <property name="allowUrlPatterns">
          <list>
            <!-- 微信认证 -->
            <value>/</value>
            <!-- 微信认证 -->
            <value>/coreServlet</value>
            <!-- 登陆 -->
            <value>/user/login/*.*</value>
            <value>/user/logout/*.*</value>
            <!-- 微信相关api -->
            <value>/wechat/*.*</value>
            <!-- 微信会员端 -->
            <value>/memberCenter/*.*</value>
            <!-- 移动端员工 -->
            <value>/staff/*.*</value>
            <!-- 后台新增平台门店 -->
            <value>/storeinfo/action/addstore</value>
            <value>/mobile/test</value>
            <value>/sms/auth/callback</value>
            <value>/storeapply/*.*</value>
            <value>/storedetail/*.*</value>
            <value>/agentapply/*.*</value>
            <value>/agentdetail/*.*</value>
            <value>/wechat/common/*.*</value>
          </list>
        </property>
      </bean>
    </mvc:interceptor>
  </mvc:interceptors>
</beans>

  走到这里,就可以在service中使用@Transactional进行事物管理,但是,在spring父上下文中,我们扫描了Controller和Service,在dispatcher-servlet.xml也即是子容器中我们同样扫描了这两者,那么问题就来了

  因为我们的事物是在父容器中,而整个服务器在进行加载配置文件的时候呢,由于服务器启动时的加载配置文件的顺序为web.xml---spring-context.xml(Spring的配置文件)---servlet-context.xml(SpringMVC的配置文件),由于spring-context.xml配置文件中Controller会先进行扫描装配,此时service进行事务增强处理,得到的将是经过事务加强处理的Service,但是子容器继续扫描,由于事物管理在父容器中声明,自容器一旦扫描,就将service重新定义为没有事物的service

  可能有点不理解啊,其实很简单,Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,而SpringMVC(对应mvc_dispatcher_servlet.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,即没有事务处理能力的Service,而父容器进行初始化的Service是保证事务的增强处理能力的.

  也即是说,我们只要让父容器扫描service而自容器不扫描service就可以了,至于controller无关紧要,因为就算是子容器第二次进行扫描了controller,其中的service也是父容器扫描的,是具有事物的.

  解决这个问题有两种方式了:

  1.父容器不扫描controller,但扫描service,子容器扫描controller但不扫描service,那么父-子容器的配置就变成了:

	<!-- 自动扫描组件,此处为父容器,这里要把 controller去除,他们是spring-context.xml中配置的,如果不去除会影响事务管理的。 -->
	<context:component-scan base-package="com.zefun.web,com.zefun.wechat,com.zefun.app">
		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
	</context:component-scan>

  <!-- 此处为自容器,扫描所有的controller 但是不扫描service,否则事物管理会失效 -->
  <context:component-scan base-package="com.zefun.web,com.zefun.wechat,com.zefun.app">
    <context:include-filter type="annotation"
      expression="org.springframework.stereotype.Controller" />
    <context:exclude-filter type="annotation"
      expression="org.springframework.stereotype.Service" />
  </context:component-scan>

  2.第二种,父子都扫描,但是呢,子容器在扫描的时候,不可以扫描service,保留父扫描过的service.只改动自容器:

  <context:component-scan base-package="com.zefun.web,com.zefun.wechat,com.zefun.app">
    <context:exclude-filter type="annotation"
      expression="org.springframework.stereotype.Service" />
  </context:component-scan>

猜你喜欢

转载自blog.csdn.net/u014201191/article/details/50428187