Springcloud 多数库 多数据源整合,查询动态切换数据库

/ Java / 1 条评论 / 5679浏览

随着SAAS平台越来越盛行,很多情况下,希望应用程序搭建一套,为每个用户建立一个私有的数据库,所有程序使用一套.

一、 首先继承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);
        /*这里数据库项目编号来更改对应的数据源*/
    }
}

(2)测试代码

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

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

测试说明:按本项目测试,需要创建4个数据库ds01、ds02、ds03、ds04,sql语句在sql.sql中,里面表都一样,只是数据不一样,如下图(如有什么不对的对方,可以加Q群 147960493,联系群主我)

  1. 1

    1123123123123

    回复