zoukankan      html  css  js  c++  java
  • springMVC+Mybatis(使用AbstractRoutingDataSource实现多数据源切换时)事务管理未生效的解决办法

    业务场景:

      A、B两个单位,系统部署同一套代码;

      A、B两系统能相互访问;

      要求将数据从A系统同步到B系统,再将反馈信息回发给A;

    实际开发情况:

      因为系统比较小,最开始设计架构的时候没有考虑到消息互通的方式,也没有设计分布式部署,所以采用AbstractRoutingDataSource灵活切换数据源的方式直接在业务代码中实现数据交互。

      

    项目代码:

    applicationContext-common.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:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	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-4.1.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd 
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
        <context:component-scan base-package="com.fms;com.job;com.jmda;">
            <context:exclude-filter type="regex" expression=".controller.*"/>
        </context:component-scan>
    	<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    
    		<property name="templateLoaderPaths">
    			<list>
    				<value>/WEB-INF/pages/</value>
    				<value>/WEB-INF/template/</value>
    				<value>classpath:/jmda-ftl/</value>
    			</list>
    		</property>
    		<property name="freemarkerSettings">
    			<props>
    				<prop key="template_update_delay">0</prop>
    				<prop key="default_encoding">UTF-8</prop>
    				<prop key="number_format">0.##########</prop>
    				<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
    				<prop key="classic_compatible">true</prop>
    				<prop key="template_exception_handler">ignore</prop>
    			</props>
    		</property>
    	</bean>
    
    	<!-- 配置c3p0数据源 -->
    	<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    		<property name="jdbcUrl">
    		<value><![CDATA[jdbc:mysql://192.168.5.186:3306/fms-ybj?useUnicode=yes&characterEncoding=UTF8]]></value>
    		</property>
    		<property name="driverClass" value="com.mysql.jdbc.Driver" />
    		<property name="user" value="root" />
    		<property name="password" value="root" />
    		<property name="maxPoolSize" value="200" />
    		<property name="minPoolSize" value="1" />
    		<property name="initialPoolSize" value="1" />
    		<property name="maxIdleTime" value="30" />
    		<property name="acquireIncrement" value="5" />
    		<property name="maxStatements" value="0" />
    		<property name="idleConnectionTestPeriod" value="60" />
    		<property name="acquireRetryAttempts" value="30" />
    		<property name="breakAfterAcquireFailure" value="true" />
    		<property name="testConnectionOnCheckout" value="false" />
    	</bean>
    	<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    		<property name="jdbcUrl">
    		 <value><![CDATA[jdbc:mysql://192.168.5.186:3306/fms-zhs?useUnicode=yes&characterEncoding=UTF8]]></value>
    		</property>
    		<property name="driverClass" value="com.mysql.jdbc.Driver" />
    		<property name="user" value="root" />
    		<property name="password" value="root" />
    		<property name="maxPoolSize" value="200" />
    		<property name="minPoolSize" value="1" />
    		<property name="initialPoolSize" value="1" />
    		<property name="maxIdleTime" value="30" />
    		<property name="acquireIncrement" value="5" />
    		<property name="maxStatements" value="0" />
    		<property name="idleConnectionTestPeriod" value="60" />
    		<property name="acquireRetryAttempts" value="30" />
    		<property name="breakAfterAcquireFailure" value="true" />
    		<property name="testConnectionOnCheckout" value="false" />
    	</bean>
    	
          <bean id="multipleDataSource" class="com.fms.common.datasource.MultipleDataSource">
              <property name="defaultTargetDataSource" ref="dataSource1"/>
              <property name="targetDataSources">
                  <map>
                      <entry key="dataSource1" value-ref="dataSource1"/>
                      <entry key="dataSource2" value-ref="dataSource2"/>
                  </map>
              </property>
          </bean>
    	<bean id="msqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<property name="dataSource" ref="multipleDataSource"/>
    		<property name="configLocation" value="classpath:mybatis.xml" />
    		<property name="mapperLocations">
    			<list>
    				<value>classpath*:/com/fms/**/dao/*Mapper.xml</value>
    				<value>classpath*:/com/fms/**/dao/*DAO.xml</value>
    			</list>
    		</property>
    	</bean>
    	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<property name="basePackage" value="com.fms.**.dao" />
    		<property name="sqlSessionFactory" ref="msqlSessionFactory" />
    	</bean>
    
    	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<property name="dataSource" ref="multipleDataSource" />
    	</bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
        <!-- 文件上传配置 -->    <bean id="multipartResolver"   class="org.springframework.web.multipart.commons.CommonsMultipartResolver">   <property name="maxUploadSize" value="10240000"/>   <property name="maxInMemorySize" value="10240000" /> </bean> </beans>

      

    springmvc-servlet.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:aop="http://www.springframework.org/schema/aop"
    	xmlns:cache="http://www.springframework.org/schema/cache"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
    	xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang"
    	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm"
    	xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mongo="http://www.springframework.org/schema/data/mongo"
    	xmlns:util="http://www.springframework.org/schema/util"
    	xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd    
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd    
            http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.1.xsd    
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd    
            http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.1.xsd    
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd    
            http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd    
            http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.1.xsd    
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd    
            http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.1.xsd    
            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd    
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
    
        <context:component-scan base-package="com.fms;com.job;com.jmda;" />      
    <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/plain;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="true" /> <property name="suffix" value=".ftl" /> <property name="contentType" value="text/html;charset=UTF-8"></property> <property name="requestContextAttribute" value="request" /> <property name="exposeSpringMacroHelpers" value="true" /> <property name="exposeRequestAttributes" value="true" /> <property name="exposeSessionAttributes" value="true" /> </bean> <mvc:resources mapping="/static/**" location="/static/" /> <mvc:resources mapping="/jmda-static/**" location="/jmda-static/" /> <mvc:resources mapping="/assets/**" location="/assets/" /> <mvc:interceptors> <bean class="com.fms.common.listener.SecurityInterceptor"/> </mvc:interceptors> </beans>

      web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app metadata-complete="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>
    		classpath*:/spring/applicationContext*.xml
    		</param-value>
    	</context-param>
    	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    	<listener>
    		<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    	</listener>
    	<filter>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    		<init-param>
    			<param-name>encoding</param-name>
    			<param-value>UTF-8</param-value>
    		</init-param>
    	</filter>
    
    	<filter-mapping>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    	<servlet>
    		<servlet-name>spring4mvc</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath*:/spring/springmvc-servlet.xml</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>spring4mvc</servlet-name>
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    	<context-param>
    		<param-name>log4jConfigLocation</param-name>
    		<param-value>/WEB-INF/classes/log4j.properties</param-value>
    	</context-param>
    	<listener>
    		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    	</listener>
        <listener>
            <listener-class>com.fms.common.listener.CommListener</listener-class>
        </listener>
    	<!-- 400错误 -->
    	<error-page>
    		<error-code>400</error-code>
    		<location>/error</location>
    	</error-page>
    	<!-- 404 页面不存在错误 -->
    	<error-page>
    		<error-code>404</error-code>
    		<location>/error</location>
    	</error-page>
    	<!-- 403 服务器拒绝请求 -->
    	<error-page>
    		<error-code>403</error-code>
    		<location>/error</location>
    	</error-page>
    	<!-- 500 服务器内部错误 -->
    	<error-page>
    		<error-code>500</error-code>
    		<location>/error</location>
    	</error-page>
    	<!-- 503 服务不可用 -->
    	<error-page>
    		<error-code>503</error-code>
    		<location>/error</location>
    	</error-page>
    	<!-- java.lang.Exception -->
    	<error-page>
    		<exception-type>java.lang.Exception</exception-type>
    		<location>/error</location>
    	</error-page>
    	<!-- java.lang.NullPointerException -->
    	<error-page>
    		<exception-type>java.lang.NullPointerException</exception-type>
    		<location>/error</location>
    	</error-page>
    	<error-page>
    		<exception-type>javax.servlet.ServletException</exception-type>
    		<location>/error</location>
    	</error-page>
    	<welcome-file-list>
    	<welcome-file></welcome-file>
    	</welcome-file-list>
    </web-app>
    

      

    MultipleDataSource:

    package com.fms.common.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class MultipleDataSource extends AbstractRoutingDataSource {
    
        private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
    
        public static void setDataSourceKey(String dataSource) {
            dataSourceKey.remove();
        }
     
       
        public static void setDataSource(String dataSource){
        	dataSourceKey.remove();
        	dataSourceKey.set(dataSource);
        }
        public static String getKey(){
        	return dataSourceKey.get();
        }
        @Override
        protected Object determineCurrentLookupKey() {
            return dataSourceKey.get();
        }
    
    }
    

      

    简略的业务代码

            @Transactional(rollbackFor = { Exception.class })
    	@Override
    	public void test() {
    		//默认数据源为datasource1
    		
    		//do some SQL operate
    		
    		//切换数据源
    		MultipleDataSource.setDataSource("datasource2");
    		
    		//do other SQL operate
    		
    		//…… ……
    	}	    
    

      

      代码进行到上面的阶段,各项业务在正常情况下能够顺利执行,但是在发生异常时出现了事务无法回滚的情况,于是我在网上找各种方法尝试修改;

      一开始我以为是AbstractRoutingDataSource多数据源的问题,一直从这方面找答案,找了很多例子修改后都仍然无法正常开启事务管理,偶然一次看到一个帖子讲spring父子容器配置,看完后照着改完,然后重新启动项目,结果真的成功了。

      具体修改如下:

    applicationContext-common.xml中:

    <!-- 
     <context:component-scan base-package="com.fms;com.job;com.jmda;">
            <context:exclude-filter type="regex" expression=".controller.*"/>
        </context:component-scan>
    
    -->
    
    改为:
    
        <context:component-scan base-package="com.fms;com.job;com.jmda;">
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    

    springmvc-servlet.xml中:

    <!-- 
    <context:component-scan base-package="com.fms;com.job;com.jmda;" />
    -->
    
    改为:
    
        <context:component-scan base-package="com.fms;com.job;com.jmda;" >      
       		 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />    
    	</context:component-scan>
    

    原因:

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

      参考博文:http://blog.csdn.net/will_awoke/article/details/12002705

    经过测试,发现了新的问题:

      在做了上述处理之后,项目事务生效了,但是多数据源切换却出现问题不能切换了,DataSourceTransactionManager只能管理一个数据源的事务,如果想要实现动态切换数据源就需要放弃spring的事务管理,在网上找了很久终于找到了一个解决方案:atomikos+jta进行分布式事务管理。

      参考博文:http://www.blogjava.net/zuxiong/archive/2015/09/24/427471.html

  • 相关阅读:
    DeFi之道丨科普:一分钟了解以太坊layer2扩容
    4.B-Air302(NB-IOT)-功能扩展-Android扫码绑定Air302,并通过MQTT实现远程控制和监控PLC(三菱Fx1s-10MR)
    Air302(NB-IOT)-硬件BUG-电路硬件错误及其修改说明
    4.2-Air302(NB-IOT)-自建MQTT服务器-Android扫码绑定Air302,并通过MQTT实现远程通信控制
    Android 开发:数据库操作-android 使用 litepal 操作本地数据库
    0.5-Air302(NB-IOT)-连接自建MQTT服务器
    0.1-Air302(NB-IOT)-刷AT指令固件
    STM32+ESP8266+AIR202基本控制篇-00-开发板回收说明
    21-STM32+ESP8266+AIR202/302远程升级方案-扩展应用-移植远程升级包实现STM32F407VET6+串口网络模组(ESP8266/Air202/Air302)使用http或者https远程升级单片机程序
    数据处理思想和程序架构: 单片机STM32F407xx/F405xx/F415xx/417xx系列flash存储方案
  • 原文地址:https://www.cnblogs.com/LiQ0116/p/6971609.html
Copyright © 2011-2022 走看看