一、多数据源如何整合
1、问题
直接引入dynamic-datasource-spring-boot-starter 和 shardingsphere-jdbc-core-spring-boot-starter,系统中会有两个数据源,一个是 DynamicDataSource,另一个是ShardingSphereDataSource。
核心问题 :ShardingSphere的数据源(ShardingSphereDataSource)没有被动态数据源(DynamicDataSource)管理,所以没法在它们之间切换。
解决方案:把ShardingSphere的数据源,当成一个普通数据源,注册到你的动态数据源里。这样,动态数据源就能统一管理它了。
2、ShardingSphereAutoConfiguration 解析
如果配置了分片规则,则会创建分片数据源,bean 名称为shardingSphereDataSource,否则查看容器中是否有 DataSoure 类型的 Bean,会创建一个无分片规则的聚合数据源,bean 名称为dataSource。
java
@Configuration
@ComponentScan("org.apache.shardingsphere.spring.boot.converter")
@EnableConfigurationProperties(SpringBootPropertiesConfiguration.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@RequiredArgsConstructor
public class ShardingSphereAutoConfiguration implements EnvironmentAware {
private String databaseName;
private final SpringBootPropertiesConfiguration props;
private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
/**
* Get mode configuration.
*
* @return mode configuration
*/
@Bean
public ModeConfiguration modeConfiguration() {
return null == props.getMode() ? null : new YamlModeConfigurationSwapper().swapToObject(props.getMode());
}
/**
* Get ShardingSphere data source bean.
*
* @param rules rules configuration
* @param modeConfig mode configuration
* @return data source bean
* @throws SQLException SQL exception
*/
@Bean
@Conditional(LocalRulesCondition.class)
@Autowired(required = false)
public DataSource shardingSphereDataSource(final ObjectProvider<List<RuleConfiguration>> rules, final ObjectProvider<ModeConfiguration> modeConfig) throws SQLException {
Collection<RuleConfiguration> ruleConfigs = Optional.ofNullable(rules.getIfAvailable()).orElseGet(Collections::emptyList);
return ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig.getIfAvailable(), dataSourceMap, ruleConfigs, props.getProps());
}
/**
* Get data source bean from registry center.
*
* @param modeConfig mode configuration
* @return data source bean
* @throws SQLException SQL exception
*/
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource dataSource(final ModeConfiguration modeConfig) throws SQLException {
return !dataSourceMap.isEmpty() ? ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig, dataSourceMap, Collections.emptyList(), props.getProps())
: ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig);
}
@Override
public final void setEnvironment(final Environment environment) {
dataSourceMap.putAll(DataSourceMapSetter.getDataSourceMap(environment));
databaseName = DatabaseNameSetter.getDatabaseName(environment);
}
}
java
public final class LocalRulesCondition extends SpringBootCondition {
private static final String SHARDING_PREFIX = "spring.shardingsphere.rules";
@Override
public ConditionOutcome getMatchOutcome(final ConditionContext conditionContext, final AnnotatedTypeMetadata annotatedTypeMetadata) {
return PropertyUtil.containPropertyPrefix(conditionContext.getEnvironment(), SHARDING_PREFIX)
? ConditionOutcome.match()
: ConditionOutcome.noMatch("Can't find ShardingSphere rule configuration in local file.");
}
}
3、DruidDynamicDataSourceConfiguration 解析
容器中如果没有 DataSource 类型的 Bean,则会创建一个DynamicRoutingDataSource,
java
@Slf4j
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class, name = "com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure")
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class, DynamicDataSourceHealthCheckConfiguration.class})
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration implements InitializingBean {
private final DynamicDataSourceProperties properties;
private final List<DynamicDataSourcePropertiesCustomizer> dataSourcePropertiesCustomizers;
public DynamicDataSourceAutoConfiguration(
DynamicDataSourceProperties properties,
ObjectProvider<List<DynamicDataSourcePropertiesCustomizer>> dataSourcePropertiesCustomizers) {
this.properties = properties;
this.dataSourcePropertiesCustomizers = dataSourcePropertiesCustomizers.getIfAvailable();
}
@Bean
public DynamicDataSourceProvider ymlDynamicDataSourceProvider() {
return new YmlDynamicDataSourceProvider(properties.getDatasource());
}
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
// ...
}
AbstractDataSourceProvider:从名字可以看出来,用来提供数据源
DynamicDataSourceAutoConfiguration会注册一个YmlDynamicDataSourceProvider,用来读取我们在配置文件中配置的数据源信息,然后创建出来。
java
@Slf4j
@AllArgsConstructor
public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {
/**
* 所有数据源
*/
private final Map<String, DataSourceProperty> dataSourcePropertiesMap;
@Override
public Map<String, DataSource> loadDataSources() {
return createDataSourceMap(dataSourcePropertiesMap);
}
}
DynamicRoutingDataSource 会注入注册的所有 DynamicDataSourceProvider,在afterPropertiesSet方法实现了数据源的创建,调用DynamicDataSourceProvider 的loadDataSources方法加载数据源信息
afterPropertiesSet 方法是在当前 Bean 的所有属性(依赖)都注入完成之后,但在 Bean 正式投入使用之前执行。
java
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
/**
* 所有数据库¬
*/
private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
// ...
@Autowired
private List<DynamicDataSourceProvider> providers;
@Override
public void afterPropertiesSet() throws Exception {
// 检查开启了配置但没有相关依赖
checkEnv();
// 添加并分组数据源
Map<String, DataSource> dataSources = new HashMap<>();
for (DynamicDataSourceProvider provider : providers) {
dataSources.putAll(provider.loadDataSources());
}
for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
addDataSource(dsItem.getKey(), dsItem.getValue());
}
// 检测默认数据源是否设置
if (groupDataSources.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);
} else if (dataSourceMap.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);
} else {
log.warn("dynamic-datasource initial loaded [{}] datasource,Please add your primary datasource or check your configuration", dataSources.size());
}
}
}
二、多数据源整合
我们自己的数据源配置一定需要在DynamicDataSourceAutoConfiguration和ShardingSphereAutoConfiguration之前执行,手动接管数据源的创建过程,如果让默认的配置先跑,会造成数据源注册混乱。
主要配置两个方面,将DynamicRoutingDataSource作为首选的数据源,然后是添加一个加载数据源的方法,将shardingSphereDataSource交给动态数据源管理。
java
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, ShardingSphereAutoConfiguration.class})
@Slf4j
public class DynamicDataSourceConfig {
/**
* 将shardingDataSource放到了多数据源(dataSourceMap)中
*/
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider(@Qualifier("shardingSphereDataSource") DataSource shardingSphereDataSource) {
return new AbstractDataSourceProvider() {
@Override
public Map<String, DataSource> loadDataSources() {
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put(DbConstant.SHARDING_DATA_SOURCE_NAME, shardingSphereDataSource);
return dataSourceMap;
}
};
}
/**
* 将动态数据源设置为首选的
* 当spring存在多个数据源时, 自动注入的是首选的对象
*
* @return
*/
@Primary
@Bean
public DataSource dynamicRoutingDataSource(DynamicDataSourceProperties dynamicDataSourceProperties) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
dataSource.setStrict(dynamicDataSourceProperties.getStrict());
dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
dataSource.setSeata(dynamicDataSourceProperties.getSeata());
return dataSource;
}
}