一:流程图解
灵活动态的切换数据源,每次在执行一个Dao操作之前可以设置当前的数据源。
二:实现原理
1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)
从AbstractRoutingDataSource的源码中
我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,
resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:
public class DynamicDataSource extends AbstractRoutingDataSource { /** * override determineCurrentLookupKey * Description: 自动查找datasource */ @Override protected Object determineCurrentLookupKey() { //从自定义的位置获取数据源标识 return DynamicDataSourceHolder.getDataSource(); } }
DynamicDataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:
public class DynamicDataSourceHolder { /** * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
//获取数据源 public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } //设置数据源 public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } //清空数据源 public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
那么setDataSource方法要在什么地方执行呢?
我们可以应用Spring aop来设置,把配置的数据源类型都设置成注解标签,在Servive层中需要切换数据源的方法上,写上注解标签,调用相应的方法切换数据源
@DataSource(name=DataSource.slave1) public List getProducts(){
import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource{ String name() default DataSource.master; public static String master="datasource1"; public static String slave1="datasource2"; publid static String slave2="datasource3"; }
三:配置文件
项目中单独分离出application-database.xml,关于数据源配置的文件
<?xml version="1.0" encoding="UTF-8"?> <!-- Spring 数据库相关配置 放在这里 --> <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: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/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"> <property name="url" value="${db1.url}"/> <property name = "user" value = "${db1.user}"/> <property name = "password" value = "${db1.pwd}"/> <property name="autoReconnect" value="true"/> <property name="useUnicode" value="true"/> <property name="characterEncoding" value="UTF-8"/> </bean> <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"> <property name="url" value="${db2.url}"/> <property name = "user" value = "${db2.user}"/> <property name = "password" value = "${db2.pwd}"/> <property name="autoReconnect" value="true"/> <property name="useUnicode" value="true"/> <property name="characterEncoding" value="UTF-8"/> </bean> <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"> <property name="url" value="${db3.url}"/> <property name = "user" value = "${db3.user}"/> <property name = "password" value = "${db3.pwd}"/> <property name="autoReconnect" value="true"/> <property name="useUnicode" value="true"/> <property name="characterEncoding" value="UTF-8"/> </bean>
</
beans
>
配置多数据源映射关系
<!-- 配置多数据源映射关系 -->
<bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"></entry>
<entry key="dataSource2" value-ref="dataSource2"></entry>
<entry key="dataSource3" value-ref="dataSource3"></entry>
</map>
</property>
<!-- 默认目标数据源为你主库数据源 -->
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
SessionFactory的配置还是照旧,使用以前的配置,只不过当前选择的数据源是datasource,也就是数据源选择的中间层MultipleDataSourceToChoose,因为当前的中间层中实现了DataSource这个接口,所以可以看做为DataSource的是实现类啦,所以配置不会出现问题。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" lazy-init="false"> <property name="configLocation" value="classpath:caravaggio-mybatis-config.xml"></property> <property name="dataSource" ref="dataSource" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="com.zmeng.rinascimento.caravaggio.mapper" />
</bean>
使用AOP拦截特定的注解去动态的切换数据源
@Order(1)如果有多个拦截器时,第一个执行
@Aspect @Slf4j @Component @Order(1) public class DataSourceAop { //@within在类上进行设置 //@annotation 在方法上进行设置 @Pointcut("@within(com.zmeng.rinascimento.caravaggio.common.util.datasource.DataSource)||@annotation(com.zmeng.rinascimento.caravaggio.common.util.datasource.DataSource)") public void pointcut(){} @Before("pointcut()") public void doBefore(JoinPoint joinPoint)throws Exception{ Class clazz = joinPoint.getTarget().getClass(); String methodName = joinPoint.getSignature().getName(); Class[] parameterTypes = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes(); Method method = clazz.getMethod(methodName,parameterTypes); //获取方法上的注解 DataSource dataSource = method.getAnnotation(DataSource.class); if(dataSource==null){ //获取类上的注解 dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class); if(dataSource==null){ return; } } //获取注解上的数据源信息 String dataSourceKey = dataSource.value(); if(dataSourceKey!=null){ //给当前执行SQL操作设置特殊的数据源的信息 DynamicDataSourceHolder.setDataSource(dataSourceKey); System.out.println(DynamicDataSourceHolder.getDataSource()); } log.info("AOP动态切换数据源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默认数据源":dataSourceKey); } @After("pointcut()") public void after(JoinPoint joinPoint){ //清理掉当前的数据源,让默认的数据不受影响 System.out.println(DynamicDataSourceHolder.getDataSource()); DynamicDataSourceHolder.clearDataSource(); } }
如果不使用aop进行拦截切换数据源,也可以在代码中进行手动的切换数据源
DynamicDataSourceHolder.setDataSource("dataSource");