hekf2022的gravatar头像
hekf2022 2019-10-16 23:39:02
Springboot动态切换数据库

很多情况下,希望应用程序搭建一套,为每个用户建立一个私有的数据库,所有程序使用一套.

开动吧:

一、 首先继承AbstractRoutingDataSource,从名称上看为抽象路由数据源,就是spring为提供动态数据库而设定的。在这个类中,需要重写determineCurrentLookupKey这个方法,这个方法就是动态从

private Map<Object, Object> targetDataSources(AbstractRoutingDataSource类里面的属性)里面获取对应的数据源
 

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
@Configuration
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 每次请求动态请求哪一个数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
 
    public DynamicDataSource(){
         Map<Object, Object> dataSources=new ConcurrentHashMap<>();
        for(int i=1;i<=4;i++){
            DataSource dataSource = druidDataSource(i);
            dataSources.put(String.valueOf(i),dataSource);
            if(i==1){
                super.setDefaultTargetDataSource(dataSource);
            }
        }
        super.setTargetDataSources(dataSources);
    }
    /**
     * 此处数据库信配置,可以来源于redis等,然后再初始化所有数据源
     * 重点说明:一个DruidDataSource数据源,它里面本身就是线程池了,
     * 所以我们不需要考虑线程池的问题
     * @param no
     * @return
     */
    public DataSource druidDataSource(int no) {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl("jdbc:mysql://localhost:3306/ds0"+no);
        datasource.setUsername("root");
        datasource.setPassword("123456");
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setInitialSize(5);
        datasource.setMinIdle(5);
        datasource.setMaxActive(20);
        //datasource.setDbType("com.alibaba.druid.pool.DruidDataSource");
        datasource.setMaxWait(60000);
        datasource.setTimeBetweenEvictionRunsMillis(60000);
        datasource.setMinEvictableIdleTimeMillis(300000);
        datasource.setValidationQuery("SELECT 1 FROM DUAL");
        datasource.setTestWhileIdle(true);
        datasource.setTestOnBorrow(false);
        datasource.setTestOnReturn(false);
        try {
            datasource.setFilters("stat,wall,log4j");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }
}

二、新建DataSourceHolder:

public class DataSourceHolder {
    //线程本地环境
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    //设置数据源,动态切换,就是调用这个setDataSource方法
    public static void setDataSource(String customerType) {
        dataSources.set(customerType);
    }
    //获取数据源
    public static String getDataSource() {
        return (String) dataSources.get();
    }
    //清除数据源
    public static void clearDataSource() {
        dataSources.remove();
    }
}

三、在controller层的每个方法前切换数据源,用到AOP

重点说明:每多人会有疑问,springcloud的controller是单例,这样在多用户的情况下,会不会窜请求,线程不安全

原因解答:(1)请看上面,其实我们使用了ThreadLocal,如果不理解的,请去补ThreadLocal知识了

用多线程测试:我用多个线程,调用同一个查询方法,但每个请求的header中的dsNo都不一样,这样就真实模拟生产环境,多用户查的情况,看是否有有窜请求,线程不安全的情况,实际测试并没有这种情况出现

(1)AOP动态切换数据库:
 

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
 
@Aspect
@Order(1)
@Configuration
public class DataSourceAspect {
    private static final String dsNo="dsNo";//数据库编号 从header中取
 
    /**
     * 切入点,放在controller的每个方法上进行切入,更新数据源
     */
    @Pointcut("execution(* com.eck.auto.controller..*.*(..))")
    private void anyMethod(){}//定义一个切入点
    @Before("anyMethod()")
    public void dataSourceChange()
    {
        //请求头head中获取对应数据库编号
        String no = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(dsNo);
        System.out.print("当前数据源编号:"+no);
        if(StringUtils.isEmpty(no)){
            //TODO 根据业务抛异常
        }
        DataSourceHolder.setDataSource(no);
        /*这里数据库项目编号来更改对应的数据源*/
    }
}

四、相信很多人都希望要本项目代码,本项目githup地址

https://github.com/hekf2021/autodatasource.git

测试说明:按本项目测试,需要创建4个数据库ds01、ds02、ds03、ds04,sql语句在sql.sql中,里面表都一样,只是数据不一样,比如ds01库表中的记录reason写dso1,ds02数据库中表的记录写ds02,ds03数据库中表的记录写ds03,ds04数据库中表的记录写ds04,以此类推,自己修改数据...,通过查询传不同的数据库编号,返回的不同数据,来辨别是否切换到了对应的数据库

如下图(如有什么不对的对方,可以加Q群 147960493,联系群主我)

补充信息:

在实际项目中如何应用,提示一下大家,1、首先得有一个公共全局数据库,如all_database_url,这里面存所有数据源的jdbc url地址,以及连对应数据库的用户名及密码。2、修改DynamicDataSource 中的DynamicDataSource()方法从all_database_url库中取出所有数据库的jdbc url信息,进行初始化。3、DynamicDataSource()方法从all_database_url库中取出所有数据库的jdbc url信息,怎么取?自己写原生代码查询数据all_database_url,不能用框架了,原因是框架连数据库,给ds01,ds02,ds03这些具体数据库用了。
 


打赏
最近浏览
epeng89 2021年12月17日
暂无贡献等级
hefenyuan91  LV11 2021年7月1日
crazy11crazy  LV30 2021年6月21日
尹恒yingying  LV18 2021年4月6日
893889486 2021年4月6日
暂无贡献等级
spring123spring  LV5 2021年3月2日
wangdongtai  LV31 2021年2月6日
无花空折枝  LV9 2021年2月3日
小板砖开瓢 2021年2月2日
暂无贡献等级
JinghanHe  LV8 2020年12月21日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友