内容简介:5 次查看本文介绍两种动态切换数据库的方法。方法一:数据源信息配置在xml中,适用于一般数据库切换。执行完某操作,切换数据库,执行另一个操作。
5 次查看
本文介绍两种动态切换数据库的方法。
方法一:数据源信息配置在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" 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"> <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"> <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> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <property name="basePackage" value="com.wbb.mapper"></property> </bean> <!-- 添加事务管理 --> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="transactionManager" > <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
/**
-
数据源bean构建器
*/
public class DataSourceBean { private final String beanName; //注册在spring中bean名字
private final String driverClassName;
private final String url;
private final String username;
private final String password;
private final String validationQuery;
private final Boolean testOnBorrow; public String getBeanName() {
return beanName;
} public String getDriverClassName() {
return driverClassName;
} public String getUrl() {
return url;
} public String getUsername() {
return username;
} public String getPassword() {
return password;
} public String getValidationQuery() {
return validationQuery;
} public Boolean getTestOnBorrow() {
return testOnBorrow;
} public DataSourceBean(DataSourceBeanBuilder builder) {
this.beanName = builder.getBeanName();
this.driverClassName = builder.getDriverClassName();
this.url = builder.getUrl();
this.username = builder.getUsername();
this.password = builder.getPassword();
this.validationQuery = builder.getValidationQuery();
this.testOnBorrow = builder.getTestOnBorrow();
} public static class DataSourceBeanBuilder {
private String beanName;
private String driverClassName = “com.mysql.jdbc.Driver”;
private String url = “jdbc:mysql://%s:%s/%s”;
private String databaseIP;
private String databasePort;
private String databaseName;
private String username;
private String password;
private String validationQuery = “select 1”;
private Boolean testOnBorrow = true;
public DataSourceBeanBuilder(String beanName, String databaseIP, String databasePort, String databaseName,
String username, String password) {
super();
this.beanName = beanName;
this.databaseIP = databaseIP;
this.databasePort = databasePort;
this.databaseName = databaseName;
this.username = username;
this.password = password;
}
public DataSourceBeanBuilder() {
super();
}
public DataSourceBeanBuilder driverClassName(String driverClassName) {
this.driverClassName = driverClassName;
return this;
}
public DataSourceBeanBuilder validationQuery(String validationQuery) {
this.validationQuery = validationQuery;
return this;
}
public DataSourceBeanBuilder testOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
return this;
}
public String getUrl() {
return String.format(url,this.databaseIP,this.databasePort,this.databaseName);
}
public String getBeanName() {
return beanName;
}
public String getDriverClassName() {
return driverClassName;
}
public String getDatabaseIP() {
return databaseIP;
}
public String getDatabasePort() {
return databasePort;
}
public String getDatabaseName() {
return databaseName;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getValidationQuery() {
return validationQuery;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
}
4.这里将操作抽离出,建立数据源操作类,方便操作
/**
- 数据源操作类
*/
public class DataSourceContext { private static ThreadLocal threadLocal = new InheritableThreadLocal(); /**- 获取数据源
/ public static DataSourceBean getDataSource() { return threadLocal.get(); } / * - 设置数据源
/ public static void setDataSource(DataSourceBean dataSourceBean) { threadLocal.set(dataSourceBean); } / * - 清除数据源
- 清除后,数据源为默认时间
*/
public static void toDefault() {
threadLocal.remove();
}
}
- 获取数据源
- 5.定义AbstractRoutingDataSource的继承类DynamicDataSource,同时实现ApplicationContextAware接口,因为该方法切换数据源,需要到spring上下文中生成和获取数据源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
讲到这里,本篇文章到此也就结束了,如果文章中有问题,或者有一些不够严谨完善的地方,希望大家体谅体谅。欢迎大家留言,交流交流。
最后附上本项目github的地址
————————————————
版权声明:本文为CSDN博主「WuBinBin-Albert」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013034378/article/details/81455513
微信赞赏 支付宝赞赏
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 动态切换数据源原理
- 苞米豆-多数据源 2.3.4 发布:优化底层,支持嵌套切换
- 苞米豆-多数据源 2.3.4 发布:优化底层,支持嵌套切换
- Spring Profile和Mybatis进行多个数据源(H2和Mysql)的切换
- 搞定 Spring Boot 多数据源(二):动态数据源
- 数据源管理 | 基于JDBC模式,适配和管理动态数据源
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。