动态数据源下运行中切换数据源的数据库

1.缘起

源自一个匪夷所思的需求:系统需要频繁采集大量(单表可能超过1亿)外部数据做审核,由于单机mysql自身限制,数据量大了之后查询一次都是问题,因此准备改造成处理一批,归档一批,也分析过分库分表分区方案,这里不在对比,有时间的话再开一篇讨论。同时,可以随时切换到归档库查看历史数据,并且还可以在归档库继续办业务,还可以对归档库归档。

2.基础框架

  1. springboot 2.6.2
  2. dynamic-datasource-spring-boot-starter 4.2.0
  3. mybatis 2.2.0
  4. spring-cloud-alibaba 2021.1
  5. druid 1.2.20

其他的就不再啰嗦,需要主要springcloud-alibaba和springboot的版本对应,可以去官网查看。

3.设计思路

  1. 用户输入归档使用的新库名(推荐默认值:原库_{年月日})
  2. 备份整个库
  3. 生成备份记录表(主要记录:原库名、新库名、时间等)
  4. 归档库仍归档到原服务器上(至关重要),当然,也可以完整的实现数据源配置入库,略复杂,考虑安全性
  5. 提供【切换数据库】操作

4.目标

系统运行过程中,本身master数据源查的是mos库,需要通过点击【切换数据库】按钮,将master数据源切换为查询mos_20240421库。即仅修改数据库名,其他配置信息依然使用原来的。

5.实现原理

由于我们本来就使用了多数据源,多数据源的原理是系统启动的时候已将数据源初始化完成,将多个数据源存放到DynamicRoutingDataSource中,可以跟进断点,看到最终使用的数据源是DruidDataSource,因此,只需修改DynamicRoutingDataSource中master对应的jdbcurl中的dbname就可以了。

示例

@RequestMapping("change/{db}")  
public void changeDataSource(@PathVariable String db) {  
  
    String newDatabase = "1".equals(db) ? "mos" : "mos_bak";  
  
    Map<String, DynamicRoutingDataSource> dataSourceMap = beanFactory.getBeansOfType(DynamicRoutingDataSource.class);  
    for (Map.Entry<String, DynamicRoutingDataSource> entry : dataSourceMap.entrySet()) {  
        DynamicRoutingDataSource dynamicRoutingDataSource = entry.getValue();  
        Map<String, DataSource> stringDataSourceMap = dynamicRoutingDataSource.getDataSources();  
        for (Map.Entry<String, DataSource> dataSourceEntry : stringDataSourceMap.entrySet()) {  
            String datasourceKey = dataSourceEntry.getKey();  
            ItemDataSource itemDataSource = (ItemDataSource) dataSourceEntry.getValue();  
            DruidDataSource druidDataSource = (DruidDataSource) itemDataSource.getDataSource();  
            if ("master".equals(datasourceKey)) {  
                DruidDataSource newDatasource = druidDataSource.cloneDruidDataSource();  
                String jdbcUrl = newDatasource.getRawJdbcUrl();  
                newDatasource.setUrl(replaceDatabase(jdbcUrl, newDatabase));  
                itemDataSource.setDataSource(newDatasource);  
                stringDataSourceMap.put("master", itemDataSource);  
            }  
        }  
    }  
}  
  
private static String replaceDatabase(String jdbcUrlString, String newDatabase){  
    String oldDatabaseName = currentDatabaseName(jdbcUrlString);  
    return jdbcUrlString.replace(oldDatabaseName,newDatabase);  
}  
  
private static String currentDatabaseName(String jdbcUrlString){  
    String[] a = jdbcUrlString.split("\\?");  
    return a[0].substring(a[0].lastIndexOf("/")+1);  
}  
  
@RequestMapping("get")  
public AjaxResult currentDataSource(){  
    Map<String,String> result = new HashMap<>();  
    Map<String, DynamicRoutingDataSource> dataSourceMap = beanFactory.getBeansOfType(DynamicRoutingDataSource.class);  
    for (Map.Entry<String, DynamicRoutingDataSource> entry : dataSourceMap.entrySet()) {  
        DynamicRoutingDataSource dynamicRoutingDataSource = entry.getValue();  
        Map<String, DataSource> stringDataSourceMap = dynamicRoutingDataSource.getDataSources();  
        for (Map.Entry<String, DataSource> dataSourceEntry : stringDataSourceMap.entrySet()) {  
            String datasourceKey = dataSourceEntry.getKey();  
            ItemDataSource itemDataSource = (ItemDataSource) dataSourceEntry.getValue();  
            DruidDataSource druidDataSource = (DruidDataSource) itemDataSource.getDataSource();  
            result.put(datasourceKey,currentDatabaseName(druidDataSource.getRawJdbcUrl()));  
        }  
    }  
    return AjaxResult.success(result);  
}  
  
@Override  
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {  
    this.beanFactory = (DefaultListableBeanFactory) beanFactory;  
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容