1.缘起
源自一个匪夷所思的需求:系统需要频繁采集大量(单表可能超过1亿)外部数据做审核,由于单机mysql自身限制,数据量大了之后查询一次都是问题,因此准备改造成处理一批,归档一批,也分析过分库分表分区方案,这里不在对比,有时间的话再开一篇讨论。同时,可以随时切换到归档库查看历史数据,并且还可以在归档库继续办业务,还可以对归档库归档。
2.基础框架
-
springboot 2.6.2
-
dynamic-datasource-spring-boot-starter 4.2.0
-
mybatis 2.2.0
-
spring-cloud-alibaba 2021.1
-
druid 1.2.20
其他的就不再啰嗦,需要主要springcloud-alibaba和springboot的版本对应,可以去官网查看。
3.设计思路
-
用户输入归档使用的新库名(推荐默认值:原库_{年月日})
-
备份整个库
-
生成备份记录表(主要记录:原库名、新库名、时间等)
-
归档库仍归档到原服务器上(至关重要),当然,也可以完整的实现数据源配置入库,略复杂,考虑安全性
-
提供【切换数据库】操作
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 ;
-
}