Spring项目中使用两种方法动态切换数据源,多数据源切换

栏目: IT技术 · 发布时间: 4年前

内容简介: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

赞赏

Spring项目中使用两种方法动态切换数据源,多数据源切换 微信赞赏 Spring项目中使用两种方法动态切换数据源,多数据源切换 支付宝赞赏

Spring项目中使用两种方法动态切换数据源,多数据源切换

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

解密硅谷

解密硅谷

[美]米歇尔 E. 梅西纳(Michelle E. Messina)、乔纳森 C. 贝尔(Jonathan C. Baer) / 李俊、李雪 / 机械工业出版社 / 2018-12 / 50.00

《解密硅谷》由身处硅谷最中心的连续创业者米歇尔·梅西纳和资深的投资人乔纳森·贝尔联合撰写,二人如庖丁解牛一般为读者深入剖析硅谷成功的原因:从硅谷的创新机制、创业生态、投资领域的潜规则、秘而不宣的价值观等角度,让阅读本书的人能够在最短的时间内,拥有像硅谷人一样的商业头脑,从而快速发现机遇,顺利地躲过创业的坑,熬过创业生死挑战中的劫数,带领初创公司顺利地活下去,并实现快速增长。 如果初创公司能够......一起来看看 《解密硅谷》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具