業務場景:html
A、B兩個單位,系統部署同一套代碼;java
A、B兩系統能相互訪問;mysql
要求將數據從A系統同步到B系統,再將反饋信息回發給A;web
實際開發狀況:spring
由於系統比較小,最開始設計架構的時候沒有考慮到消息互通的方式,也沒有設計分佈式部署,因此採用AbstractRoutingDataSource靈活切換數據源的方式直接在業務代碼中實現數據交互。sql
項目代碼:express
applicationContext-common.xml:spring-mvc
<?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:mybatis
<?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