SpringBoot 实现动态数据源
功能: 前端请求接口时携带用户信息,后端拦截器获取用户信息后切换数据源查询数据。
使用场景:多租户,sass,pass等项目。
实现原理:主要通过SpringBoot提供的AbstractRoutingDataSource类,该类允许我们存入一个map,然后在通过数据源获取数据前允许我们指定一个key,然后自动的根据key从map内取对应的数据源。
1 创建 DataSourceHolder类管理数据源的创建和存储
@Component
@NoArgsConstructor
@Slf4j
public class DataSourceHolder {
@Autowired
private AbstractRoutingDataSource routingDataSource;
@Resource
private DataSourceInfoMapper dataSourceInfoMapper;
protected final ConcurrentHashMap<Object, Object> dataSourceMap = new ConcurrentHashMap<>(64);
private final ConcurrentHashMap<String, Object> dataSourceInitLock = new ConcurrentHashMap<>(64);
private static ThreadLocal<String> currDataSourceKey = new ThreadLocal<>();
private static ThreadLocal<Boolean> useDefaultDs = new ThreadLocal<>();
public static void setDataSourceKey(String dataSourceKey){
log.info("数据源切换:{} -> {}", currDataSourceKey.get(), dataSourceKey );
currDataSourceKey.set(dataSourceKey);
unUseDefaultDataSource();
}
public static void useDefaultDataSource(){
useDefaultDs.set(true);
}
public static void unUseDefaultDataSource(){
useDefaultDs.remove();
}
public String getDataSourceKey(){
final String currentDsKey = currDataSourceKey.get();
if( Objects.equals(useDefaultDs.get(),true) || StrUtil.isBlank( currentDsKey ) ){
log.info("采用默认数据源");
return null;
}
if( dataSourceMap.containsKey(currentDsKey) ){
log.info("采用动态数据源,dataSourceKey:{}",currentDsKey);
return currentDsKey;
}
return initDataSource( currentDsKey );
}
private String initDataSource( final String dataSourceKey ) {
Object lockObj = dataSourceInitLock.computeIfAbsent(dataSourceKey, key -> new Object());
synchronized ( lockObj ){
log.info("开始初始化数据源,dataSourceKey:{}",dataSourceKey);
if( dataSourceMap.containsKey(dataSourceKey) ){
log.info("数据源已被其他线程初始化,dataSourceKey:{}",dataSourceKey);
return dataSourceKey;
}
try {
useDefaultDataSource();
LambdaQueryWrapper<DataSourceInfo> wrapper = Wrappers.lambdaQuery();
wrapper.eq(DataSourceInfo::getDataSourceKey,dataSourceKey);
wrapper.last( "limit 1");
DataSourceInfo dataSourceInfo = dataSourceInfoMapper.selectOne(wrapper);
if( dataSourceInfo == null ){
log.error("加载数据源失败,数据源配置信息不存在,dataSourceKey:{}",dataSourceKey);
return null;
}
Class dataSourceType = Class.forName( dataSourceInfo.getDataSourceType() );
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(dataSourceInfo.getDriverClassName())
.username(dataSourceInfo.getUsername())
.password(dataSourceInfo.getPassword())
.url(dataSourceInfo.getUrl())
.type(dataSourceType)
.build();
dataSourceMap.put(dataSourceKey,dataSource);
refreshDataSource();
return dataSourceKey;
}catch( ClassNotFoundException ignored){
} finally {
unUseDefaultDataSource();
}
}
return null;
}
public void deleteDataSource( String dataSourceKey ){
dataSourceMap.remove(dataSourceKey);
refreshDataSource();
}
public void refreshDataSource(){
log.info("刷新动态数据源");
routingDataSource.afterPropertiesSet();
}
}
2 继承AbstractRoutingDataSource实现动态数据源
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
@Resource
private DataSourceHolder dataSourceHolder;
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.getDataSourceKey();
}
}
3 配置数据源
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DynamicDataSourceConfiguration {
@Autowired
private DataSourceHolder dataSourceHolder;
@Bean("defaultDataSource")
public DataSource defaultDataSource(DataSourceProperties dataSourceProperties){
return DataSourceBuilder.create()
.driverClassName(dataSourceProperties.getDriverClassName())
.username(dataSourceProperties.getUsername())
.password(dataSourceProperties.getPassword())
.url(dataSourceProperties.getUrl())
.type(dataSourceProperties.getType())
.build();
}
@Primary
@Bean("dataSource")
public DynamicDataSource dynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource( SpringUtil.getBean("defaultDataSource") );
dynamicDataSource.setTargetDataSources( dataSourceHolder.dataSourceMap );
return dynamicDataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
4 创建过滤器 根据请求动态切换数据源
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter((servletRequest, servletResponse, filterChain) -> {
DataSourceHolder.useDefaultDataSource();
Object dsKey = servletRequest.getParameter("dsKey");
if( dsKey!=null ){
DataSourceHolder.setDataSourceKey(dsKey.toString());
}
filterChain.doFilter(servletRequest,servletResponse);
});
bean.setOrder(1);
bean.addUrlPatterns("/*");
return bean;
}
}
代码已经放大Gitee上,代码地址:https://gitee.com/GJW520/dynamic-datasource.git
|