一、前言
最近參與了一個大公司的wms項(xiàng)目,采用 SpringCloud 技術(shù)棧開發(fā)育灸,公司國內(nèi)國外有非常多的工廠,針對每個國家單獨(dú)部署一套wms系統(tǒng)昵宇,大體的邏輯相通描扯,只有個別調(diào)整。
項(xiàng)目的數(shù)據(jù)庫是這樣設(shè)計(jì)的趟薄,每個國家的所有人員信息存到一個庫中绽诚,稱為基礎(chǔ)資料(base_info)庫,每個國家的每個工廠單獨(dú)啟用一個數(shù)據(jù)庫。
所以中國區(qū)的項(xiàng)目數(shù)據(jù)庫結(jié)構(gòu)類似這樣:
base_info
db_0
db_1
db_2
db_sc
db_sd
...
但是項(xiàng)目中是這樣做多數(shù)據(jù)源配置的:
- 首先在配置文件中配置國內(nèi)所有的數(shù)據(jù)庫信息;
- 為每個數(shù)據(jù)庫配置創(chuàng)建一個數(shù)據(jù)源Bean恩够;
- 創(chuàng)建一個動態(tài)數(shù)據(jù)源 Bean 卒落,將上步創(chuàng)建的所有數(shù)據(jù)源注入到動態(tài)數(shù)據(jù)源中;
- 創(chuàng)建會話工廠 Bean蜂桶、事務(wù)管理器 Bean儡毕;
項(xiàng)目在人員登錄的時候做了配置,可以自動將數(shù)據(jù)源切換為人員對應(yīng)的數(shù)據(jù)庫上扑媚。
這里就產(chǎn)生了問題腰湾,國內(nèi)那么多的工廠,需要配置那么多的數(shù)據(jù)庫疆股,并且手動配置那么多的數(shù)據(jù)源费坊,雖然可以實(shí)現(xiàn)數(shù)據(jù)源切換,但是繁瑣的配置比較費(fèi)勁旬痹,所以我稱之為此種配置方式為 “靜態(tài)多數(shù)據(jù)源”附井。
二、動態(tài)多數(shù)據(jù)源
2.1 什么是動態(tài)多數(shù)據(jù)源
簡單來說两残,可以在應(yīng)用運(yùn)行中永毅,將數(shù)據(jù)源動態(tài)生成并可以切換使用。
2.2 動態(tài)多數(shù)據(jù)源好處
- 省去了創(chuàng)建大量的 Bean 的操作人弓;
- 可以在運(yùn)行時添加數(shù)據(jù)源沼死;
三、動態(tài)多數(shù)據(jù)源的配置
廢話不多說崔赌,直接上干貨意蛀。
3.1 創(chuàng)建動態(tài)數(shù)據(jù)源
通過實(shí)現(xiàn)Spring提供的AbstractRoutingDataSource
類,我們可以實(shí)現(xiàn)自己的數(shù)據(jù)源選擇邏輯峰鄙,從而可以實(shí)現(xiàn)數(shù)據(jù)源的動態(tài)切換。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Value("${spring.datasource.default-db-key}")
private String defaultDbKey;·
@Override
protected Object determineCurrentLookupKey() {
String currentDb = DynamicDataSourceService.currentDb();
if (currentDb == null) {
return defaultDbKey;
}
return currentDb;
}
}
3.2 創(chuàng)建動態(tài)數(shù)據(jù)源配置類
跟配置靜態(tài)多數(shù)據(jù)源一樣太雨,需要手動配置下面的三個 Bean吟榴,只不過DynamicDataSource
類的targetDataSources
是空的。
@Configuration
public class DynamicDataSourceConfig {
/**
* 動態(tài)數(shù)據(jù)源
*/
@Bean
public DynamicDataSource dynamicDataSource() {
DynamicDataSource dataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
dataSource.setTargetDataSources(targetDataSources);
return dataSource;
}
/**
* 會話工廠
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
sqlSessionFactoryBean.setConfiguration(configuration);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/repository/*.xml"));
return sqlSessionFactoryBean;
}
/**
* 事務(wù)管理器
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
3.3 創(chuàng)建動態(tài)數(shù)據(jù)源服務(wù)類
這是一個比較核心的工具類,提供了一些靜態(tài)方法從而可以實(shí)現(xiàn)一些功能囊扳,包括:
動態(tài)添加數(shù)據(jù)源吩翻、切換數(shù)據(jù)源、重置數(shù)據(jù)源锥咸、獲取數(shù)據(jù)源狭瞎。
在我們 3.1 中創(chuàng)建的 DynamicDataSource
類中,我們就是調(diào)用了 DynamicDataSourceService
類的 switchDb
方法實(shí)現(xiàn)的數(shù)據(jù)源選擇搏予。
通過查看下面的代碼就能看出來使用線程本地的技術(shù)實(shí)現(xiàn)的多個請求數(shù)據(jù)源互不相干熊锭。
public class DynamicDataSourceService {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceService.class);
private static final Map<Object, Object> dataSources = new HashMap<>();
private static final ThreadLocal<String> dbKeys = ThreadLocal.withInitial(() -> null);
/**
* 動態(tài)添加一個數(shù)據(jù)源
*
* @param name 數(shù)據(jù)源的key
* @param dataSource 數(shù)據(jù)源對象
*/
public static void addDataSource(String name, DataSource dataSource) {
DynamicDataSource dynamicDataSource = App.context.getBean(DynamicDataSource.class);
dataSources.put(name, dataSource);
dynamicDataSource.setTargetDataSources(dataSources);
dynamicDataSource.afterPropertiesSet();
log.info("添加了數(shù)據(jù)源:{}",name);
}
/**
* 切換數(shù)據(jù)源
*/
public static void switchDb(String dbKey) {
dbKeys.set(dbKey);
}
/**
* 重置數(shù)據(jù)源
*/
public static void resetDb() {
dbKeys.remove();
}
/**
* 獲取當(dāng)前數(shù)據(jù)源
*/
public static String currentDb() {
return dbKeys.get();
}
}
至此,動態(tài)多數(shù)據(jù)源的配置就完成了,我們只需要編寫數(shù)據(jù)源生成的邏輯碗殷,在程序運(yùn)行時調(diào)用 addDataSource
方法即可將數(shù)據(jù)源動態(tài)添加到上下文中精绎,并支持動態(tài)切換。
下面簡單介紹一下基于配置文件的數(shù)據(jù)源生成锌妻。
四代乃、數(shù)據(jù)源生成器
我自定義了一個數(shù)據(jù)源生成器接口用于定義動態(tài)生成數(shù)據(jù)源的要求。
public interface DataSourceProvider {
List<DataSource> provide();
}
然后編寫了一個根據(jù)配置文件提供數(shù)據(jù)源并配置的實(shí)現(xiàn)類仿粹。
@Component
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public class YmlDataSourceProvider implements DataSourceProvider {
private List<Map<String, DataSourceProperties>> dataSources;
private DataSource buildDataSource(DataSourceProperties prop) {
DataSourceBuilder<?> builder = DataSourceBuilder.create();
builder.driverClassName(prop.getDriverClassName());
builder.username(prop.getUsername());
builder.password(prop.getPassword());
builder.url(prop.getJdbcUrl());
return builder.build();
}
@Override
public List<DataSource> provide() {
List<DataSource> res = new ArrayList<>();
dataSources.forEach(map -> {
Set<String> keys = map.keySet();
keys.forEach(key -> {
DataSourceProperties properties = map.get(key);
DataSource dataSource = buildDataSource(properties);
DynamicDataSourceService.addDataSource(key, dataSource);
});
});
return res;
}
@PostConstruct
public void init() {
provide();
}
public List<Map<String, DataSourceProperties>> getDataSources() {
return dataSources;
}
public void setDataSources(List<Map<String, DataSourceProperties>> dataSources) {
this.dataSources = dataSources;
}
}
看一下對應(yīng)的配置文件內(nèi)容:
spring:
datasource:
default-db-key: db0
hikari:
data-sources:
- db0:
jdbc-url: jdbc:mysql://47.96.172.163:3306/test
username: admin
password: 199711
driver-class-name: com.mysql.cj.jdbc.Driver
- db1:
jdbc-url: jdbc:mysql://47.96.172.163:3306/test2
username: admin
password: 199711
driver-class-name: com.mysql.cj.jdbc.Driver
這樣就實(shí)現(xiàn)了應(yīng)用啟動時自動將配置文件中的數(shù)據(jù)源配置讀取并生成數(shù)據(jù)源注冊到上下文中搁吓。
當(dāng)然也可以有其他的實(shí)現(xiàn),比如從數(shù)據(jù)庫讀取并配置吭历,或者通過接口請求的方式生成都可以堕仔,只要實(shí)現(xiàn)自己的 DataSourceProvider
就可以了。
五毒涧、后記
動態(tài)配置多數(shù)據(jù)源到這里就結(jié)束了贮预,一些需要優(yōu)化的地方可以自己修改,比如使用切面的形式將選擇數(shù)據(jù)源的邏輯抽離等等契讲。
借助 Spring 的生態(tài)仿吞,許多的工作都變得異常簡單。