最近一個項目用到了多個數據庫,所以需要實現動態切換數據源來查詢數據,http://www.cnblogs.com/lzrabbit/p/3750803.html這篇文章讓我受益匪淺,提供了一種自動切換數據源的思路,但這種方式不支持事務,所以我進一步改進了這個方案,下面直入正題
多數據源配置:
#============================================================================ # DataBaseOne #============================================================================ jdbc.one.driver=com.mysql.jdbc.Driver jdbc.one.url=jdbc:mysql://127.0.0.1:3306/DataBaseOne?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.one.username=root jdbc.one.password=root ============================================================================= #============================================================================ # DataBaseTwo #============================================================================ jdbc.two.driver=com.mysql.jdbc.Driver jdbc.two.url=jdbc:mysql://127.0.0.1:3306/DataBaseTwo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.two.username=root jdbc.two.password=root ============================================================================= #============================================================================ # DataBaseThree #============================================================================ jdbc.three.driver=com.mysql.jdbc.Driver jdbc.three.url=jdbc:mysql://127.0.0.1:3306/DataBaseThree?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true jdbc.three.username=root jdbc.mysql.password=root ============================================================================= #============================================================================ # 通用配置 #============================================================================ jdbc.initialSize=5 jdbc.minIdle=5 jdbc.maxIdle=20 jdbc.maxActive=100 jdbc.maxWait=100000 jdbc.defaultAutoCommit=false jdbc.removeAbandoned=true jdbc.removeAbandonedTimeout=600 jdbc.testWhileIdle=true jdbc.timeBetweenEvictionRunsMillis=60000 jdbc.numTestsPerEvictionRun=20 jdbc.minEvictableIdleTimeMillis=300000
Spring中使用多數據源:
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 自己根據情況補全其他配置,以下只提供了數據源配置 --> <!-- 多數據源配置 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"/> </bean> <!-- 第一個數據源dataSourceOne --> <bean id="dataSourceOne" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.one.driver}"/> <property name="url" value="${jdbc.one.url}"/> <property name="username" value="${jdbc.one.username}"/> <property name="password" value="${jdbc.one.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <property name="maxWait" value="${jdbc.maxWait}"/> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean> <!-- 第二個數據源dataSourceTwo --> <bean id="dataSourceTwo" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.two.driver}"/> <property name="url" value="${jdbc.two.url}"/> <property name="username" value="${jdbc.two.username}"/> <property name="password" value="${jdbc.two.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <property name="maxWait" value="${jdbc.maxWait}"/> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean> <!-- 第三個數據源dataSourceThree --> <bean id="dataSourceThree" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.three.driver}"/> <property name="url" value="${jdbc.three.url}"/> <property name="username" value="${jdbc.three.username}"/> <property name="password" value="${jdbc.three.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <property name="maxWait" value="${jdbc.maxWait}"/> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean> <!-- 使用自己實現的數據源實現類MultipleDataSource,這個類隨意放在哪個包下都行 --> <bean id="multipleDataSource" class="com.cnblogs.datasource.MultipleDataSource"> <!-- 設置默認的數據源 --> <property name="defaultTargetDataSource" ref="dataSourceOne"/> <property name="targetDataSources"> <map> <!-- 這個key是對應數據源的別稱,通過這個key可以找到對應的數據源,value-ref就是上面數據源的id --> <entry key="dataSourceOneKey" value-ref="dataSourceOne"/> <entry key="dataSourceTwoKey" value-ref="dataSourceTwo"/> <entry key="dataSourceThreeKey" value-ref="dataSourceThree"/> </map> </property> </bean> <!-- 讓spring使用我們配置的多數據源 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="multipleDataSource"/> </bean> <!-- mybatis.spring自動映射 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.cnblogs.mapper"/> </bean> <!-- 自動掃描,多個包以 逗號分隔 --> <context:component-scan base-package="com.cnblogs.**"/> <!-- AOP配置(事務控制) --> <aop:config> <!--pointcut元素定義一個切入點,execution中的第一個星號 用以匹配方法的返回類型, 這里星號表明匹配所有返回類型。 com.abc.service.*.*(..)表明匹配com.abc.service包下的所有類的所有方法 --> <aop:pointcut id="myPointcut" expression="execution(* com.cnblogs.service.*.*(..))" /> <!--將定義好的事務處理策略應用到上述的切入點 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" /> </aop:config> </beans>
MultipleDataSource.java實現:
package com.cnblogs.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** 多數據源java實現 */ public class MultipleDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>(); public static void setDataSourceKey(String dataSource) { dataSourceKey.set(dataSource); } @Override protected Object determineCurrentLookupKey() { return dataSourceKey.get(); } }
下面詳解使用SpringAOP來實現數據源切換,並支持事務控制
上面我們配置的AOP是針對service層的,所以在調用service層的任何方法時都會經過AOP,因此我們就在AOP中先於service調用時把數據源切換,看代碼
新建類MultipleDataSourceAspectAdvice,把MultipleDataSourceAspectAdvice.java放在spring能自動注入的包中,比如controller包,或者自己把這個類所在的包加入到component-scan配置中去,總之要要讓spring能自動加載
package com.cnblogs.controller; import org.apache.log4j.Logger; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import com.cnblogs.datasource.MultipleDataSource; import com.cnblogs.service.DataBaseOne; import com.cnblogs.service.DataBaseTwo; import com.cnblogs.service.DataBaseThree; /** * 數據庫鏈接自動切換AOP處理 * Order優先級設置到最高,因為在所有service方法調用前都必須把數據源確定 * Order數值越小優先級越高 */ @Component @Aspect @Order(1) public class MultipleDataSourceAspectAdvice { private static final Logger LOGGER = Logger.getLogger(MultipleDataSourceAspectAdvice.class); public MultipleDataSourceAspectAdvice() { LOGGER.info("MultipleDataSourceAspectAdvice 加載成功"); } /** * 定義切面 */ @Pointcut("execution(* com.cnblogs.service.*.*(..))") public void pointCut() { } // dataSourceOneKey // dataSourceTwoKey // dataSourceThreeKey @Around("pointCut()") public Object doAround(ProceedingJoinPoint jp) throws Throwable { if (jp.getTarget() instanceof DataBaseOne) { LOGGER.debug("使用數據庫鏈接:dataSourceOneKey"); MultipleDataSource.setDataSourceKey("dataSourceOneKey"); } else if (jp.getTarget() instanceof DataBaseTwo) { LOGGER.debug("使用數據庫鏈接:dataSourceTwoKey"); MultipleDataSource.setDataSourceKey("dataSourceTwoKey"); } else if (jp.getTarget() instanceof DataBaseThree) { LOGGER.debug("使用數據庫鏈接:dataSourceThreeKey"); MultipleDataSource.setDataSourceKey("dataSourceThreeKey"); } else { // 默認是dataSourceOneKey LOGGER.debug("使用數據庫鏈接:dataSourceOneKey"); MultipleDataSource.setDataSourceKey("dataSourceOneKey"); } return jp.proceed(); } }
到這里我們所以的spring多數據源配置已經完畢,那如何在執行service方法時讓service切換到正確的數據庫呢?上面的類中定義了有3個類DataBaseOne,DataBaseTwo,DataBaseThree,這3個類其實只是一個interface,沒有任何實現方法,我們讓具體業務的service都繼承至這3個類以區分不同的service對應不同的數據源,因為業務的service我是知道他用的哪個數據源的,比如FooService繼承至DataBaseOne,則在使用FooService任何方法時AOP就會先把數據源切換到dataSourceOneKey,以此就達到了自動切換數據源的目的,並且支持事務,下面看代碼:
package com.cnblogs.service; /** * DataBaseOne數據庫占位類 * 詳情參見 MultipleDataSourceAspectAdvice 類 */ public interface DataBaseOne { }
DataBaseTwo,DataBaseThree與這完全相同
至此多數據源就配置完畢,可以安心寫業務邏輯了
原理部分博友http://www.cnblogs.com/lzrabbit/p/3750803.html講的十分清楚,再次感謝
