参考https://blog.csdn.net/u013034378/article/details/81455513
方法一:数据源信息配置在xml中,适用于一般数据库切换。执行完某种操作,切换数据库,执行另一个操作。
方法二:数据源信息配置在默认数据源中,适用于切换数据库操作同一方法,相当于批量执行方法。
两种方法核心都是AbstractRoutingDataSource,由spring提供,用来动态切换数据源。
主要讲下方法一。
1.继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法
package com.wbb.dataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MultiDataSource extends AbstractRoutingDataSource{
/* ThreadLocal,叫线程本地变量或线程本地存储。
* ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
* 这里使用它的子类InheritableThreadLocal用来保证父子线程都能拿到值。
*/
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
/**
* 设置dataSourceKey的值
* @param dataSource
*/
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
/**
* 清除dataSourceKey的值
*/
public static void toDefault() {
dataSourceKey.remove();
}
/**
* 返回当前dataSourceKey的值
*/
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
- 配置XML,这里将上面创建的MultiDataSource注入到spring容器中,这里主要将AbstractRoutingDataSource的两个属性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默认目标数据源,targetDataSources(map类型)存放用来切换的数据源。配置完以后,其他地方用到数据源的话,都引用multiDataSource。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="10" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<!-- 这里建议配置为TRUE,防止取到的连接不可用 testOnBorrow:申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn:归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 -->
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 开启Druid的监控统计功能 -->
<property name="filters" value="stat"></property>
</bean>
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName1}" />
<property name="url" value="${jdbc.url1}" />
<property name="username" value="${jdbc.username1}" />
<property name="password" value="${jdbc.password1}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="10" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 开启Druid的监控统计功能 -->
<property name="filters" value="stat"></property>
</bean>
<bean id="multiDataSource"
class="com.wbb.dataSource.MultiDataSource">
<property name="defaultTargetDataSource" ref="dataSource"></property>
<property name="targetDataSources">
<map>
<entry key="dataSource1" value-ref="dataSource1"></entry>
</map>
</property>
</bean>
3…手动切换数据源,切换完以后,记得再切回默认数据库。
MultiDataSource.setDataSourceKey("dataSource1");//切换到dataSource1数据源
XXX在该数据源下的操作XXX
MultiDataSource.toDefault();//操作完以后,清除dataSourceKey的值,即切回默认数据源,原理后面会讲。
4… 利用aop切换数据源,这里记得开启aop,配置文件中使用<aop:aspectj-autoproxy />
4.1首先定义一个注解,来调用注解切换数据库
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicRoutingDataSource {
String value() default "dataSource";//本文默认dataSource
}
4.2 这里使用@Before和@After,在调用目标方法前,进行aop拦截,通过解析注解上的值来切换数据源。在调用方法结束后,切回默认数据源。如果目标方法无返回值,也可以使用@Around,调用ProceedingJoinPoint的proceed()方法前切换数据源,调用proceed()方法后切回默认数据源。
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.wbb.annotation.DynamicRoutingDataSource;
import com.wbb.dataSource.MultiDataSource;
@Aspect
@Component
public class HandlerDataSourceAop {
private static Logger logger = LoggerFactory.getLogger(HandlerDataSourceAop.class);
/**
* @within匹配类上的注解
* @annotation匹配方法上的注解
*/
@Pointcut("@within(com.wbb.annotation.DynamicRoutingDataSource)||@annotation(com.wbb.annotation.DynamicRoutingDataSource)")
public void pointcut(){}
@Before(value = "pointcut()")
public void beforeOpt(JoinPoint joinPoint) {
/** 先查找方法上的注解,没有的话再去查找类上的注解
*-----------------------------------------------------------------------
* 这里使用的是接口的模式,注解在实现类上,所以不能使用如下方式获取目标方法的对象,
* 因为该方式获取的是该类的接口或者顶级父类的方法的对象.
* MethodSignature methodSignature = (MethodSignature)point.getSignature();
* Method method = methodSignature.getMethod();
* DynamicRoutingDataSource annotation = method.getAnnotation(DynamicRoutingDataSource.class);
* 通过上面代码是获取不到方法上的注解的,如果真要用上面代码来获取,可以修改aop代理模式,修改为cglib代理
* 在xml配置文件修改为<aop:aspectj-autoproxy proxy-target-class="true" /> ,
* proxy-target-class属性true为cglib代理,默认false为jdk动态代理 。
* ---------------------------------------------------------
* 本文使用是jdk动态代理, 这里使用反射的方式获取方法
*/
//反射获取Method 方法一
Object target = joinPoint.getTarget();
Class<?> clazz = target.getClass();
Method[] methods = clazz.getMethods();
DynamicRoutingDataSource annotation = null;
for (Method method : methods) {
if (joinPoint.getSignature().getName().equals(method.getName())) {
annotation = method.getAnnotation(DynamicRoutingDataSource.class);
if (annotation == null) {
annotation = joinPoint.getTarget().getClass().getAnnotation(DynamicRoutingDataSource.class);
if (annotation == null) {
return;
}
}
}
}
// 反射获取Method 方法二
// Object[] args = joinPoint.getArgs();
// Class<?>[] argTypes = new Class[joinPoint.getArgs().length];
// for (int i = 0; i < args.length; i++) {
// argTypes[i] = args[i].getClass();
// }
// Method method = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), argTypes);
// DynamicRoutingDataSource annotation = method.getAnnotation(DynamicRoutingDataSource.class);
// if (annotation == null) {
// annotation = joinPoint.getTarget().getClass().getAnnotation(DynamicRoutingDataSource.class);
// if (annotation == null) {
// return;
// }
// }
String dataSourceName = annotation.value();
MultiDataSource.setDataSourceKey(dataSourceName);
logger.info("切到" + dataSourceName + "数据库");
}
@After(value="pointcut()")
public void afterOpt(){
MultiDataSource.toDefault();
logger.info("切回默认数据库");
}
}
4.3 使用:只需要把@DynamicRoutingDataSource注解加到方法或者类上即可
@DynamicRoutingDataSource("dataSource1")
网友评论