最近一个项目用到了多个数据库, 所以需要实现动态切换数据源来查询数据, 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 讲的十分清楚, 再次感谢
来源: https://www.cnblogs.com/ieinstein/p/9049749.html