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
springMVC+Mybatis(使用AbstractRoutingDataSource实现多数据源切换时)事务管理未生效的解决办法的更多相关文章
- 【转】Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法
重构了下之前自己的一个新闻客户端,全部使用了Fragment来进行页面切换,只有一个入口Activity作为程序的启动Activity,其中有一个界面需要调用摄像头识别二维码, 于是就会用到Surfa ...
- [Android Pro] Fragment中使用SurfaceView切换时闪一下黑屏的解决办法
方法一.在Activity的onCreate中添加如下代码 getWindow().setFormat(PixelFormat.TRANSLUCENT); reference to : http:/ ...
- SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换
SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换 本文转载至:http://exceptioneye.iteye.com/blog/1698064 Spri ...
- AbstractRoutingDataSource 实现动态数据源切换原理简单分析
AbstractRoutingDataSource 实现动态数据源切换原理简单分析 写在前面,项目中用到了动态数据源切换,记录一下其运行机制. 代码展示 下面列出一些关键代码,后续分析会用到 数据配置 ...
- Spring Boot学习笔记(七)多数据源下的事务管理
DataBaseConfig中加入事务管理器 DataBaseConfig的详解以及多数据源的配置参见我的上一篇文章 @Configuration @MapperScan(basePackages={ ...
- spring+springMVC+Mybatis架构下采用AbstractRoutingDataSource、atomikos、JTA实现多数据源灵活切换以及分布式事务管理
背景: 1.系统采用SSM架构.需要在10多个MYSQL数据库之间进行切换并对数据进行操作,上篇博文<springMVC+Mybatis(使用AbstractRoutingDataSource实 ...
- SpringMVC+Mybatis 如何配置多个数据源并切换?
最近公司一个项目需要连接两个数据库(A和B)操作,有的模块查询A库,有的模块查询B库,因此需要改造下,项目后台用的是SpringMVC+Mybatis+MySQL架构,折腾了两天后终于搞定了,在这里记 ...
- AbstractRoutingDataSource实现动态数据源切换 专题
需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...
- Spring(AbstractRoutingDataSource)实现动态数据源切换--转载
原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目 ...
随机推荐
- ArrayList 线程安全
都说ArrayList是线程不安全的,那为什么不安全呢.根据官方提供的源码, 我是这样理解的,ArrayList的成员方法都不是原子操作的,比如add(E)方法,该方法是在集合的尾部加入一个一个元素. ...
- 篇2 安卓app自动化测试-初识python调用appium
篇2 安卓app自动化测试-初识python调用appium --lamecho辣么丑 1.1概要 大家好!我是lamecho(辣么丑),上一篇也是<安卓app自动化测 ...
- (知识点)JavaScript原型和原型链
〇 每个函数都拥有prototype属性,而该属性所储存的就是原型对象 1)原型属性—— 上面我们测试了foo()函数的 1) length属性(length属性除了可以用在数组中,还可以用于记录函数 ...
- SOA与基于CDIF的API的联动
几千年来,巴别塔的故事一直是人类面对的一个核心的困境.为了交流和沟通我们人类创造出语言,但沟通与交流仍然存在障碍……相同语言之间的沟通依语境的不同,尚且存在巨大的鸿沟,不同语言之间更是让人坐困愁城. ...
- C#控制台程序使用Log4net日志组件
1.Log4net一般都不陌生,但是在配置上不同类型的项目又不相同的地方比如C#控制台程序和C# MVCWeb项目,拿控制台项目为例 项目源码在文章底部 2.首先创建一个控制台程序,引入Log4n ...
- 十分钟彻底理解javascript 的 this指向,不懂请砸店
函数的this指向谁,和函数在哪里被定义的,函数在哪里被执行的没有半毛钱关系,只遵守下面的规律: 在非严格模式中: 1.自执行函数里面,this永远指向window; <script> v ...
- Qt:添加点击事件的Label并显示图片
1.给label添加点击事件 Qt中原本的label是没有点击事件的,如果想添加点击事件的话,可以继承QLabel类并重载鼠标事件(比如mousePressedEvent),然后在鼠标事件中发送一个信 ...
- openlayers应用(二):加载百度离线瓦片
上一篇文章介绍了使用openlayers3加载百度在线地图,对某些项目或应用场景比如不允许上外网的单位,某些项目只针对一定区域地图加载应用,比如一个县的地图,可以采用下载百度瓦片地图,在服务器或者本机 ...
- OC中的私有变量和私有方法
在类的实现即.m文件中也可以声明成员变量,但是因为在其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private得.在 .m中定义的成员变量不能和它的头文件.h中 ...
- git pull冲突:commit your changes or stash them before you can merge.
今天用git pull来更新代码,遇到了下面的问题: error: Your local changes to the following files would be overwritten by ...