springboot实现双数据源动态切换

1、AbstractRoutingDataSource介绍

动态数据源其实就是针对系统原本的DataSource数据源套上了一个适配器,或者叫路由

即:

AbstractRoutingDataSource

它本身实现DataSource接口,初始化时将自己实例化成数据源dataSource的bean对象

当mybatis或者其他插件获取数据源dataSource的bean对象时,就获取到它了。

(1)它内部实现了一个map用来存放多个数据源,做key和数据源的映射

(2)提供默认数据源设置

(3)提供设置数据源key的回调函数,根据key返回对应数据源(切换数据源,肯定是针对当前工作的线程,而不是全局,因此线程1切换数据源不能影响线程2正常工作,因此该key存放应存放在线程的变量中)。

(4)系统每次调用数据源的时候它都会先获取key来确定数据源。没有key或者key不存在就是默认。

2、使用

我们只在需要切换数据源的地方修改一些线程变量key即可

3、代码实现

(1)创建线程变量key


public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }
    public static void useMaster() {
        contextHolder.set("master");
    }
    public static void useSlave() {
        contextHolder.set("slave");
    }



    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
}

(2)创建动态数据源类

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDatasource  extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

(3)初始化数据源

这块代码因为发现@ConfigurationProperties注解不生效,所以就手动写的数据源配置

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceConfig {

    @Value("${spring.datasource.dynamic.datasource.master.url}")
    String masterUrl;
    @Value("${spring.datasource.dynamic.datasource.master.driver-class-name}")
    String masterDriver;
    @Value("${spring.datasource.dynamic.datasource.master.username}")
    String masterUsername;
    @Value("${spring.datasource.dynamic.datasource.master.password}")
    String masterPassword;
    @Value("${spring.datasource.dynamic.datasource.slave.url}")
    String slaveUrl;
    @Value("${spring.datasource.dynamic.datasource.slave.driver-class-name}")
    String slaveDriver;
    @Value("${spring.datasource.dynamic.datasource.slave.username}")
    String slaveUsername;
    @Value("${spring.datasource.dynamic.datasource.slave.password}")
    String slavePassword;


    @Bean("master")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
    public DataSource masterDatasource(){
        return DataSourceBuilder.create().url(masterUrl).driverClassName(masterDriver).username(masterUsername).password(masterPassword).build();
    }

    @Bean("slave")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
    public DataSource slaveDatasource(){
        return DataSourceBuilder.create().url(slaveUrl).driverClassName(slaveDriver).username(slaveUsername).password(slavePassword).build();
    }

    @Bean
    @Primary
    public DataSource dataSource(){
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("master", masterDatasource());
        dataSourceMap.put("slave", slaveDatasource());
        DynamicDatasource dynamicDatasource=new DynamicDatasource();
        dynamicDatasource.setTargetDataSources(dataSourceMap);
        dynamicDatasource.setDefaultTargetDataSource(masterDatasource());
        return dynamicDatasource;
    }
}

(4)使用样例

DynamicDataSourceContextHolder.useSlave();

DynamicDataSourceContextHolder.useMaster();

    /**
     * 从库同步给主库
     */
    @PostMapping("/slaveToMaster")
    public Object slaveToMaster(@RequestParam(name="tableName")String tableName,
                                @RequestParam(name="field")String field,
                                @RequestParam(name="whereSQL")String whereSQL) throws SQLException {
        log.info("当前功能:将从库数据同步到主库中");
        DynamicDataSourceContextHolder.useSlave();
        List<Map<String,Object>> datas=baseSQLMapper.selectById(tableName,field,whereSQL);
        log.info("查询到从库数据共{}条",datas==null?0:datas.size());
        DynamicDataSourceContextHolder.useMaster();
        if(datas!=null&& !datas.isEmpty()){
            log.info("先清理主库【{}】表的数据",tableName);
            baseSQLMapper.deleteBeforeInsert(tableName,whereSQL);
            log.info("开始同步数据:【{}】",tableName);
            String[] fields=field.split(",");
            ProgressUtils.calculate(datas, 5, new ProgressUtils.ProgressUtilsForEachCallBack<Map<String, Object>>() {
                @Override
                public void process(Map<String, Object> map, int index) {
                    List<Object> data=new ArrayList<>();
                    for (String f:fields) {
                        data.add(map.get(f.trim()));
                    }
                    baseSQLMapper.insert(tableName,field,data);
                }

                @Override
                public void notify(int progress, int index) {
                    log.info("同步【{}】进度:{}%",tableName,progress);
                }
            });
            log.info("同步完成:【{}】",tableName);
            return "传输成功";
        }else{
            return "未查询到任何数据";
        }

    }

4、到这里其实配置1000个数据源也能够正常切换,原理一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值