背景:html
一、系統採用SSM架構、須要在10多個MYSQL數據庫之間進行切換並對數據進行操做,上篇博文《springMVC+Mybatis(使用AbstractRoutingDataSource實現多數據源切換時)事務管理未生效的解決辦法》java
二、第一步先經過AbstractRoutingDataSource實現了多數據源的靈活切換,可是後來發現事務不起做用;mysql
三、發現問題是由於重複掃描service包致使第二次掃入容器的BEAN沒有歸入事務管理,所以在springMVC的配置文件中排除了對Service註解的掃描,防止重複掃描,事務生效了,測試又發現數據源不能成功切換了;web
四、查閱資料發現是因爲spring默認提供的事務管理DataSourceTransactionManager只能管理一個數據源的事務,所以考慮使用atomikos+JTA進行分佈式事務管理spring
配置文件:sql
一、jdbc.properties數據庫
配置你須要鏈接的數據庫資源express
jdbc_zhs.driverClassName=com.mysql.jdbc.Driver jdbc_zhs.url.spider=jdbc:mysql://127.0.0.1:3306/fms?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_zhs.username=root jdbc_zhs.password=root jdbc_jcs.driverClassName=com.mysql.jdbc.Driver jdbc_jcs.url.spider=jdbc:mysql://127.0.0.1:3306/fms-jcs?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_jcs.username=root jdbc_jcs.password=root jdbc_hks.driverClassName=com.mysql.jdbc.Driver jdbc_hks.url.spider=jdbc:mysql://127.0.0.1:3306/fms-hks?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_hks.username=root jdbc_hks.password=root jdbc_xts.driverClassName=com.mysql.jdbc.Driver jdbc_xts.url.spider=jdbc:mysql://127.0.0.1:3306/fms-xts?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_xts.username=root jdbc_xts.password=root jdbc_xxgcs.driverClassName=com.mysql.jdbc.Driver jdbc_xxgcs.url.spider=jdbc:mysql://127.0.0.1:3306/fms-xxgcs?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_xxgcs.username=root jdbc_xxgcs.password=root jdbc_zdhs.driverClassName=com.mysql.jdbc.Driver jdbc_zdhs.url.spider=jdbc:mysql://127.0.0.1:3306/fms-zdhs?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_zdhs.username=root jdbc_zdhs.password=root jdbc_gfs.driverClassName=com.mysql.jdbc.Driver jdbc_gfs.url.spider=jdbc:mysql://127.0.0.1:3306/fms-gfs?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_gfs.username=root jdbc_gfs.password=root jdbc_kxs.driverClassName=com.mysql.jdbc.Driver jdbc_kxs.url.spider=jdbc:mysql://127.0.0.1:3306/fms-kxs?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_kxs.username=root jdbc_kxs.password=root jdbc_fyd.driverClassName=com.mysql.jdbc.Driver jdbc_fyd.url.spider=jdbc:mysql://127.0.0.1:3306/fms-fyd?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_fyd.username=root jdbc_fyd.password=root jdbc_ybj.driverClassName=com.mysql.jdbc.Driver jdbc_ybj.url.spider=jdbc:mysql://127.0.0.1:3306/fms-ybj?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_ybj.username=root jdbc_ybj.password=root jdbc_yzh.driverClassName=com.mysql.jdbc.Driver jdbc_yzh.url.spider=jdbc:mysql://127.0.0.1:3306/fms-yzh?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_yzh.username=root jdbc_yzh.password=root
二、applicationContext-common.xml:apache
<?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: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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 啓動掃描 --> <context:component-scan base-package="com.fms;com.job;com.jmda;"> <!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> --> </context:component-scan> <!-- 文件上傳 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10240000"/> <property name="maxInMemorySize" value="10240000" /> </bean> <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> <!-- 啓用CGliB --> <aop:aspectj-autoproxy /> <!-- 配置c3p0數據源,項目中有代碼須要C3P0數據源支持時額外配置,不須要能夠不配置 --> <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="jdbcUrl"> <value><![CDATA[jdbc:mysql://localhost: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 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath*:/spring/jdbc.properties</value> </list> </property> </bean> <!-- 多個數據源的公用配置,方便下面直接引用 --> <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close" abstract="true"> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/> <property name="poolSize" value="10" /> <property name="minPoolSize" value="10"/> <property name="maxPoolSize" value="30"/> <property name="borrowConnectionTimeout" value="60"/> <property name="reapTimeout" value="20"/> <!-- 最大空閒時間 --> <property name="maxIdleTime" value="60"/> <property name="maintenanceInterval" value="60" /> <property name="loginTimeout" value="60"/> <property name="logWriter" value="60"/> <property name="testQuery"> <value>select 1</value> </property> </bean> <!-- 下面是全部須要切換的數據源 --> <!-- 綜合所數據源 --> <bean id="ds_zhs" parent="abstractXADataSource"> <!-- value只要多個XA數據源之間不重複就行,隨便取名 --> <property name="uniqueResourceName" value="mysql/zhs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_zhs.url.spider}</prop> <prop key="user">${jdbc_zhs.username}</prop> <prop key="password">${jdbc_zhs.password}</prop> </props> </property> </bean> <!-- 艦船所數據源--> <bean id="ds_jcs" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/jcs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_jcs.url.spider}</prop> <prop key="user">${jdbc_jcs.username}</prop> <prop key="password">${jdbc_jcs.password}</prop> </props> </property> </bean> <!-- 航空所數據源--> <bean id="ds_hks" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/hks" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_hks.url.spider}</prop> <prop key="user">${jdbc_hks.username}</prop> <prop key="password">${jdbc_hks.password}</prop> </props> </property> </bean> <!-- 系統所數據源--> <bean id="ds_xts" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/xts" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_xts.url.spider}</prop> <prop key="user">${jdbc_xts.username}</prop> <prop key="password">${jdbc_xts.password}</prop> </props> </property> </bean> <!-- 信息工程所數據源--> <bean id="ds_xxgcs" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/xxgcs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_xxgcs.url.spider}</prop> <prop key="user">${jdbc_xxgcs.username}</prop> <prop key="password">${jdbc_xxgcs.password}</prop> </props> </property> </bean> <!-- 自動化所數據源--> <bean id="ds_zdhs" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/zdhs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_zdhs.url.spider}</prop> <prop key="user">${jdbc_zdhs.username}</prop> <prop key="password">${jdbc_zdhs.password}</prop> </props> </property> </bean> <!-- 規範所數據源--> <bean id="ds_gfs" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/gfs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_gfs.url.spider}</prop> <prop key="user">${jdbc_gfs.username}</prop> <prop key="password">${jdbc_gfs.password}</prop> </props> </property> </bean> <!-- 科信所數據源--> <bean id="ds_kxs" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/kxs" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_kxs.url.spider}</prop> <prop key="user">${jdbc_kxs.username}</prop> <prop key="password">${jdbc_kxs.password}</prop> </props> </property> </bean> <!-- 翻譯隊數據源--> <bean id="ds_fyd" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/fyd" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_fyd.url.spider}</prop> <prop key="user">${jdbc_fyd.username}</prop> <prop key="password">${jdbc_fyd.password}</prop> </props> </property> </bean> <!-- 院本級數據源--> <bean id="ds_ybj" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/ybj" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_ybj.url.spider}</prop> <prop key="user">${jdbc_ybj.username}</prop> <prop key="password">${jdbc_ybj.password}</prop> </props> </property> </bean> <!-- 院綜合系統數據源--> <bean id="ds_yzh" parent="abstractXADataSource"> <property name="uniqueResourceName" value="mysql/yzh" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc_yzh.url.spider}</prop> <prop key="user">${jdbc_yzh.username}</prop> <prop key="password">${jdbc_yzh.password}</prop> </props> </property> </bean> <!-- 配置多數據源 MultipleDataSource--> <bean name="dynamicDatasource" class="com.fms.common.datasource.MultipleDataSource"> <property name="targetDataSources"> <map> <!-- key和value-ref儘可能保持一致,我在測試的時候由於名稱不一致一致報錯, 找了很久都沒找到緣由,最後統一了名稱終於成功啓動了 --> <entry key="ds_zhs" value-ref="ds_zhs"/> <entry key="ds_jcs" value-ref="ds_jcs"/> <entry key="ds_hks" value-ref="ds_hks"/> <entry key="ds_xts" value-ref="ds_xts"/> <entry key="ds_xxgcs" value-ref="ds_xxgcs"/> <entry key="ds_zdhs" value-ref="ds_zdhs"/> <entry key="ds_gfs" value-ref="ds_gfs"/> <entry key="ds_kxs" value-ref="ds_kxs"/> <entry key="ds_fyd" value-ref="ds_fyd"/> <entry key="ds_ybj" value-ref="ds_ybj"/> <entry key="ds_yzh" value-ref="ds_yzh"/> </map> </property> <!-- 指定一個默認的數據源,即在不須要切換數據源時,本地系統默認使用的數據源 --> <property name="defaultTargetDataSource" ref="ds_zhs" /> </bean> <!-- 下面開始配置SqlSessionFactoryBean,有多少個數據源須要支持就陪多少個 --> <bean id="sqlSessionFactory_zhs" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- dataSource的ref對應數據源的id --> <property name="dataSource" ref="ds_zhs"/> <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 id="sqlSessionFactory_jcs" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_jcs"/> <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 id="sqlSessionFactory_hks" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_hks"/> <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 id="sqlSessionFactory_xts" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_xts"/> <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 id="sqlSessionFactory_xxgcs" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_xxgcs"/> <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 id="sqlSessionFactory_zdhs" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_zdhs"/> <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 id="sqlSessionFactory_gfs" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_gfs"/> <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 id="sqlSessionFactory_kxs" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_kxs"/> <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 id="sqlSessionFactory_fyd" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_fyd"/> <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 id="sqlSessionFactory_ybj" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_ybj"/> <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 id="sqlSessionFactory_yzh" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="ds_yzh"/> <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> <!-- 配置自定義的SqlSessionTemplate模板,注入相關配置 --> <bean id="sqlSessionTemplate" class="com.fms.common.datasource.CustomSqlSessionTemplate" scope="prototype"> <!-- 構造注入參數指定本地默認數據源對應的SqlSessionFactoryBean --> <constructor-arg ref="sqlSessionFactory_zhs" /> <property name="targetSqlSessionFactorys"> <map> <!-- key和上文配置的數據源的id值儘可能保持一致,我在測試的時候由於名稱不一致一致報錯, 找了很久都沒找到緣由,最後統一了名稱終於成功啓動了 --> <entry value-ref="sqlSessionFactory_zhs" key="ds_zhs"/> <entry value-ref="sqlSessionFactory_jcs" key="ds_jcs"/> <entry value-ref="sqlSessionFactory_hks" key="ds_hks"/> <entry value-ref="sqlSessionFactory_xts" key="ds_xts"/> <entry value-ref="sqlSessionFactory_xxgcs" key="ds_xxgcs"/> <entry value-ref="sqlSessionFactory_zdhs" key="ds_zdhs"/> <entry value-ref="sqlSessionFactory_gfs" key="ds_gfs"/> <entry value-ref="sqlSessionFactory_kxs" key="ds_kxs"/> <entry value-ref="sqlSessionFactory_fyd" key="ds_fyd"/> <entry value-ref="sqlSessionFactory_ybj" key="ds_ybj"/> <entry value-ref="sqlSessionFactory_yzh" key="ds_yzh"/> </map> </property> </bean> <!-- 配置mybatis接口掃描 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.fms.**.dao" /> <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/> </bean> <!-- jta配置,直接複用,不須要修改 --> <!-- jta配置開始 --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown"> <value>true</value> </property> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"> <ref bean="atomikosTransactionManager" /> </property> <property name="userTransaction"> <ref bean="atomikosUserTransaction" /> </property> </bean> <!-- jta配置結束 --> <!-- 配置事務管理 --> <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" /> </beans>
三、springmvc-servlet.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: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.jmda;com.job;" > <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" /> </context:component-scan> <!-- <context:annotation-config/> --> <!-- message-converters --> <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> <!--文件下載 --> <servlet> <servlet-name>ServletDownload</servlet-name> <servlet-class>com.fms.business.sjtb.service.ServletDownload</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletDownload</servlet-name> <url-pattern>/servlet/download</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>
五、在resources文件夾下增長jta.properties文件:
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.console_file_name = tm.out com.atomikos.icatch.log_base_name = tmlog com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm com.atomikos.icatch.console_log_level=WARN
JAVA代碼:
1.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 changeTodefaultDataSource() { dataSourceKey.remove(); } // //院 // public static void setDataSourceY(){ // dataSourceKey.remove(); // dataSourceKey.set("yzhDataSource"); // } //將數據源設置爲配置文件中key值爲dataSource參數對應的值的數據源 public static void setDataSource(String dataSource){ dataSourceKey.remove(); dataSourceKey.set(dataSource); } //獲取當前數據源的key值 public static String getKey(){ return dataSourceKey.get(); } //重寫AbstractRoutingDataSource的方法,提供當前的數據源用於鏈接 @Override protected Object determineCurrentLookupKey() { return dataSourceKey.get(); } }
二、自定義的SqlSessionTemlate類:
CustomSqlSessionTemplate
package com.fms.common.datasource; import static java.lang.reflect.Proxy.newProxyInstance; import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; import static org.mybatis.spring.SqlSessionUtils.closeSqlSession; import static org.mybatis.spring.SqlSessionUtils.getSqlSession; import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.util.List; import java.util.Map; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.MyBatisExceptionTranslator; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.util.Assert; public class CustomSqlSessionTemplate extends SqlSessionTemplate { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; private Map<Object, SqlSessionFactory> targetSqlSessionFactorys; private SqlSessionFactory defaultTargetSqlSessionFactory; public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) { this.targetSqlSessionFactorys = targetSqlSessionFactorys; } public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; } public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration() .getEnvironment().getDataSource(), true)); } public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { super(sqlSessionFactory, executorType, exceptionTranslator); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); this.defaultTargetSqlSessionFactory = sqlSessionFactory; } @Override public SqlSessionFactory getSqlSessionFactory() { SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(MultipleDataSource.getKey()); if (targetSqlSessionFactory != null) { return targetSqlSessionFactory; } else if (defaultTargetSqlSessionFactory != null) { return defaultTargetSqlSessionFactory; } else { Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required"); Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required"); } return this.sqlSessionFactory; } @Override public Configuration getConfiguration() { return this.getSqlSessionFactory().getConfiguration(); } public ExecutorType getExecutorType() { return this.executorType; } public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { return this.exceptionTranslator; } /** * {@inheritDoc} */ public <T> T selectOne(String statement) { return this.sqlSessionProxy.<T> selectOne(statement); } /** * {@inheritDoc} */ public <T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.<T> selectOne(statement, parameter); } /** * {@inheritDoc} */ public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey); } /** * {@inheritDoc} */ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey); } /** * {@inheritDoc} */ public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds); } /** * {@inheritDoc} */ public <E> List<E> selectList(String statement) { return this.sqlSessionProxy.<E> selectList(statement); } /** * {@inheritDoc} */ public <E> List<E> selectList(String statement, Object parameter) { return this.sqlSessionProxy.<E> selectList(statement, parameter); } /** * {@inheritDoc} */ public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds); } /** * {@inheritDoc} */ public void select(String statement, ResultHandler handler) { this.sqlSessionProxy.select(statement, handler); } /** * {@inheritDoc} */ public void select(String statement, Object parameter, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, handler); } /** * {@inheritDoc} */ public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); } /** * {@inheritDoc} */ public int insert(String statement) { return this.sqlSessionProxy.insert(statement); } /** * {@inheritDoc} */ public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); } /** * {@inheritDoc} */ public int update(String statement) { return this.sqlSessionProxy.update(statement); } /** * {@inheritDoc} */ public int update(String statement, Object parameter) { return this.sqlSessionProxy.update(statement, parameter); } /** * {@inheritDoc} */ public int delete(String statement) { return this.sqlSessionProxy.delete(statement); } /** * {@inheritDoc} */ public int delete(String statement, Object parameter) { return this.sqlSessionProxy.delete(statement, parameter); } /** * {@inheritDoc} */ public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } /** * {@inheritDoc} */ public void commit() { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } /** * {@inheritDoc} */ public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } /** * {@inheritDoc} */ public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } /** * {@inheritDoc} */ public void rollback(boolean force) { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } /** * {@inheritDoc} */ public void close() { throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); } /** * {@inheritDoc} */ public void clearCache() { this.sqlSessionProxy.clearCache(); } /** * {@inheritDoc} */ public Connection getConnection() { return this.sqlSessionProxy.getConnection(); } /** * {@inheritDoc} * @since 1.0.2 */ public List<BatchResult> flushStatements() { return this.sqlSessionProxy.flushStatements(); } /** * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to * the {@code PersistenceExceptionTranslator}. */ private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final SqlSession sqlSession = getSqlSession( CustomSqlSessionTemplate.this.getSqlSessionFactory(), CustomSqlSessionTemplate.this.executorType, CustomSqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory()); } } } }
三、數據源切換切面ChooseDataSourceAspect:
package com.fms.common.aop; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import com.fms.common.annotation.ChooseDataSource; import com.fms.common.datasource.MultipleDataSource; import com.fms.common.utils.reflect.ReflectUtil; /** * 類描述:完成數據源的切換,抽類切面,具體項目繼承一下,不須要重寫便可使用 */ @Aspect public abstract class ChooseDataSourceAspect { protected static final ThreadLocal<String> preDatasourceHolder = new ThreadLocal<String>(); @Pointcut("@annotation(com.fms.common.annotation.ChooseDataSource)") public void methodWithChooseAnnotation() { System.err.println("************************************************//////////////////////////////////////////"); } /** * 根據@ChooseDataSource的屬性值設置不一樣的dataSourceKey,以供DynamicDataSource */ @Before("methodWithChooseAnnotation()") public void changeDataSourceBeforeMethodExecution(JoinPoint jp) { //拿到anotation中配置的數據源 String resultDS = determineDatasource(jp); //沒有配置實用默認數據源 if (resultDS == null) { MultipleDataSource.changeTodefaultDataSource(); return; } preDatasourceHolder.set(MultipleDataSource.getKey()); //將數據源設置到數據源持有者 MultipleDataSource.setDataSource(resultDS); } /** * 若是須要修改獲取數據源的邏輯,請重寫此方法 * * @param jp * @return */ @SuppressWarnings("rawtypes") protected String determineDatasource(JoinPoint jp) { String methodName = jp.getSignature().getName(); Class targetClass = jp.getSignature().getDeclaringType(); String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass); String dataSourceForTargetMethod = resolveDataSourceFromMethod(targetClass, methodName); String resultDS = determinateDataSource(dataSourceForTargetClass, dataSourceForTargetMethod); return resultDS; } /** * 方法執行完畢之後,數據源切換回以前的數據源。 * 好比foo()方法裏面調用bar(),可是bar()另一個數據源, * bar()執行時,切換到本身數據源,執行完之後,要切換到foo()所須要的數據源,以供 * foo()繼續執行。 */ @After("methodWithChooseAnnotation()") public void restoreDataSourceAfterMethodExecution() { MultipleDataSource.setDataSource(preDatasourceHolder.get()); preDatasourceHolder.remove(); } /** * @param targetClass * @param methodName * @return */ @SuppressWarnings("rawtypes") private String resolveDataSourceFromMethod(Class targetClass, String methodName) { Method m = ReflectUtil.findUniqueMethod(targetClass, methodName); if (m != null) { ChooseDataSource choDs = m.getAnnotation(ChooseDataSource.class); return resolveDataSourcename(choDs); } return null; } /** * 方法描述 : * 肯定最終數據源,若是方法上設置有數據源,則以方法上的爲準,若是方法上沒有設置,則以類上的爲準,若是類上沒有設置,則使用默認數據源 * * @param classDS * @param methodDS * @return */ private String determinateDataSource(String classDS, String methodDS) { // if (null == classDS && null == methodDS) { // return null; // } // 二者必有一個不爲null,若是二者都爲Null,也會返回Null return methodDS == null ? classDS : methodDS; } /** * 方法描述 : 類級別的 @ChooseDataSource的解析 * * @param targetClass * @return */ @SuppressWarnings({"unchecked", "rawtypes"}) private String resolveDataSourceFromClass(Class targetClass) { ChooseDataSource classAnnotation = (ChooseDataSource) targetClass .getAnnotation(ChooseDataSource.class); // 直接爲整個類進行設置 return null != classAnnotation ? resolveDataSourcename(classAnnotation) : null; } /** * 方法描述 : * 組裝DataSource的名字 * * @param ds * @return */ private String resolveDataSourcename(ChooseDataSource ds) { return ds == null ? null : ds.value(); } }
四、數據源切換註解ChooseDataSource:
package com.fms.common.annotation; import java.lang.annotation.*; /** * 註解式數據源,用來進行數據源切換 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ChooseDataSource { String value() default ""; }
五、使用方式:
TestService:
package com.fms.business; import java.util.Map; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.fms.common.dao.table.mapper.WfWorkflowPOMapper; import com.fms.common.datasource.MultipleDataSource; import com.fms.common.model.po.WfWorkflowPO; import com.google.common.collect.Maps; @Service(value="testService") public class TestService { //注入SqlSessionTemplate,執行自定義的Mapper.xml文件 @Autowired SqlSessionTemplate sqlSessionTemplate; String nameSpace = "com.fms.business.dao.TestServiceMapper"; @Transactional public void test() throws Exception{ Map<String, Object> param = Maps.newHashMap(); param.put("id", "123"); param.put("name", "zhangsan"); //默認數據源ds_zhs sqlSessionTemplate.insert(nameSpace+".testInsert", param); //切換至ds_jcs MultipleDataSource.setDataSource("ds_jcs"); sqlSessionTemplate.insert(nameSpace+".testInsert", param); } @Transactional public void test1() throws Exception{ Map<String, Object> param = Maps.newHashMap(); param.put("id", "124"); param.put("name", "lisi"); //默認數據源ds_zhs sqlSessionTemplate.insert(nameSpace+".testInsert", param); //切換至ds_jcs MultipleDataSource.setDataSource("ds_jcs"); sqlSessionTemplate.insert(nameSpace+".testInsert", param); //編寫拋出異常的代碼測試事務回滾 String str = null; str.trim(); } //使用MyBatis自動生成的Mapper接口 @Autowired WfWorkflowPOMapper wfWorkflowPOMapper; @Transactional public void test2() throws Exception{ WfWorkflowPO record = new WfWorkflowPO(); record.setWorkflowId("12345"); record.setName("xxxx"); wfWorkflowPOMapper.insert(record); //切換至ds_jcs MultipleDataSource.setDataSource("ds_jcs"); wfWorkflowPOMapper.insert(record); //切換至ds_yzh MultipleDataSource.setDataSource("ds_yzh"); wfWorkflowPOMapper.insert(record); } }
TestController:
package com.fms.business; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class TestController { @Autowired TestService testService; @RequestMapping("/test") @ResponseBody public String test(){ try { testService.test(); return "ok"; } catch (Exception e) { e.printStackTrace(); return "fail"; } } @RequestMapping("/test1") @ResponseBody public String test1(){ try { testService.test1(); return "ok"; } catch (Exception e) { e.printStackTrace(); return "fail"; } } @RequestMapping("/test2") @ResponseBody public String test2(){ try { testService.test2(); return "ok"; } catch (Exception e) { e.printStackTrace(); return "fail"; } } }Aspect
原例中還有使用@ChooseDataSource註解切換數據源的用法,須要AspectJ編譯,搗鼓了一下沒成功,數據源可以切換,可是分佈式事務不起做用,有興趣能夠本身試試本身調試修改。
在項目中加入一個繼承ChooseDataSourceAspect的類便可:
package com.fms.business;ChooseDataSource import org.aspectj.lang.annotation.Aspect; import com.fms.common.aop.ChooseDataSourceAspect;
@Component @Aspect public class TestAspect extends ChooseDataSourceAspect { }
參考博文:http://www.blogjava.net/zuxiong/archive/2015/09/24/427471.html