14 次查看
本文介绍两种动态切换数据库的方法。 方法一:数据源信息配置在xml中,适用于一般数据库切换。执行完某操作,切换数据库,执行另一个操作。 方法二:数据源信息配置在默认数据源中,适用于切换数据库操作同一方法,相当于批量执行方法。
两种方法核心都是AbstractRoutingDataSource,由spring提供,用来动态切换数据源。我们需要继承它,来进行操作。
源码分析可以看下一篇文章源码分析
方法一:数据源信息都配置在xml中 1…继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法
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();
}
} 2… 配置xml,这里将上面创建的MultiDataSource注入到spring容器中,这里主要用到AbstractRoutingDataSource的两个属性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默认目标数据源,targetDataSources(map类型)存放用来切换的数据源。配置完以后,其他地方用到数据源的话,都引用multiDataSource。 3…手动切换数据源,切换完以后,记得再切回默认数据库。
MultiDataSource.setDataSourceKey(“dataSource1”);//切换到dataSource1数据源 XXX在该数据源下的操作XXX MultiDataSource.toDefault();//操作完以后,清除dataSourceKey的值,即切回默认数据源,原理后面会讲。 1 2 3 4… 利用aop切换数据源,这里记得开启aop,配置文件中使用 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”) 1 方法一到此就结束了
方法二:数据源信息配置在数据库中 1.xml的配置,这里只需要配置一个默认的数据源就行了,因为其他的数据源都是从该数据源的数据源表中读取。
<?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: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-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="defaultDataSource" 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="validationQuery" value="select 1"></property>
<property name="testOnBorrow" value="true"></property>
</bean>
<!--动态数据源相关-->
<bean id="dynamicDataSource" class="com.wbb.dataSource.dynamic.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultDataSource" value-ref="defaultDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="defaultDataSource"/>
</bean>
<!-- mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:com/wbb/mapper/*.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="com.wbb.mapper"></property>
</bean>
<!-- 添加事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"></property>
</bean>
</beans>
2.数据源表的设计
DROP TABLE IF EXISTS other_datasource
;
CREATE TABLE other_datasource
(
id
int(11) NOT NULL AUTO_INCREMENT,
datasource_name
varchar(64) DEFAULT NULL,
database_ip
varchar(64) DEFAULT NULL,
database_port
varchar(64) DEFAULT NULL,
database_name
varchar(64) DEFAULT NULL,
database_username
varchar(64) DEFAULT NULL,
database_password
varchar(64) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
3.数据源表对应的bean
/**
/**
import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.alibaba.druid.pool.DruidDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource implements ApplicationContextAware{
private ApplicationContext applicationContext ;
/**
* 连接数据源前,调用该方法
*/
@Override
protected Object determineCurrentLookupKey() {
//1.获取手动设置的数据源参数DataSourceBean
DataSourceBean dataSourceBean = DataSourceContext.getDataSource();
if(dataSourceBean == null) {
return null;
}
try {
//2.获取AbstractRoutingDataSource的targetDataSources属性,该属性存放数据源属性
Map<Object, Object> targetSourceMap = getTargetSource();
synchronized(this) {
/*
* 3.判断targetDataSources中是否已经存在要设置的数据源bean
* 存在的话,则直接返回beanName
*
*/
if(!targetSourceMap.keySet().contains(dataSourceBean.getBeanName())) {
/*不存在,则进行以下几步
3.1 先在spring容器中创建该数据源bean
*/
Object dataSource = createDataSource(dataSourceBean);
//3.2 在创建后的bean,放入到targetDataSources Map中
targetSourceMap.put(dataSourceBean.getBeanName(), dataSource);
/*
* 3.3 通知spring有bean更新
* 主要更新AbstractRoutingDataSource的resolvedDefaultDataSource(Map)属性,
* 更新完以后,AbstractRoutingDataSource的determineTargetDataSource()中,才能找到数据源
* 代码如下:
* Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
*/
super.afterPropertiesSet();
}
}
for(Map.Entry<Object, Object> entry : targetSourceMap.entrySet()) {
System.out.println(entry.getKey()+"-"+entry.getValue());
}
return dataSourceBean.getBeanName();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据数据源信息在spring中创建bean,并返回
* @param dataSourceBean 数据源信息
* @return
* @throws IllegalAccessException
*/
public Object createDataSource(DataSourceBean dataSourceBean) throws IllegalAccessException {
//1.将applicationContext转化为ConfigurableApplicationContext
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
//2.获取bean工厂并转换为DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
/*
* 3.本文用的是DruidDataSource,所有在这里我们获取的是该bean的BeanDefinitionBuilder,
* 通过BeanDefinitionBuilder来创建bean定义
*/
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
/**
* 4.获取DataSourceBean里的属性和对应值,并将其交给BeanDefinitionBuilder创建bean的定义
*/
Map<String, Object> propertyKeyValues = getPropertyKeyValues(DataSourceBean.class, dataSourceBean);
for(Map.Entry<String,Object> entry : propertyKeyValues.entrySet()) {
beanDefinitionBuilder.addPropertyValue(entry.getKey(), entry.getValue());
}
//5.bean定义创建好以后,将其交给beanFactory注册成bean对象,由spring容器管理
beanFactory.registerBeanDefinition(dataSourceBean.getBeanName(), beanDefinitionBuilder.getBeanDefinition());
//6.最后获取步骤5生成的bean,并将其返回
return context.getBean(dataSourceBean.getBeanName());
}
//获取类属性和对应的值,放入Map中
@SuppressWarnings("unused")
private <T> Map<String, Object> getPropertyKeyValues(Class<T> clazz, Object object) throws IllegalAccessException {
Field[] fields = clazz.getDeclaredFields();
Map<String,Object> map = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true);
map.put(field.getName(), field.get(object));
}
map.remove("beanName");
return map;
}
//通过反射获取AbstractRoutingDataSource的targetDataSources属性
@SuppressWarnings("unchecked")
public Map<Object, Object> getTargetSource() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
field.setAccessible(true);
return (Map<Object, Object>) field.get(this);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
} 6.测试用例测试一下
@Test
public void testDynamicDataSource() {
1.从表中获取需要切换的数据源信息
List<DataSourceDO> dataSourceDOList = dataSourceMapper.getAllDataSources();
2.循环遍历,切换数据源,执行操作,切完就得切回默认数据源
for (DataSourceDO dataSourceDO : dataSourceDOList) {
DataSourceBean dataSourceBean = new DataSourceBean(new DataSourceBeanBuilder(dataSourceDO.getDatasourceName(),
dataSourceDO.getDatabaseIp(), dataSourceDO.getDatabasePort(), dataSourceDO.getDatabaseName(),
dataSourceDO.getUsername(), dataSourceDO.getPassword()));
DataSourceContext.setDataSource(dataSourceBean);
XXX你的操作XXX
DataSourceContext.toDefault();
}
}
方法二部分参考https://blog.csdn.net/yizhenn/article/details/53965552该博客的思想。
源码分析可以看下一篇文章https://blog.csdn.net/u013034378/article/details/81661706
讲到这里,本篇文章到此也就结束了,如果文章中有问题,或者有一些不够严谨完善的地方,希望大家体谅体谅。欢迎大家留言,交流交流。