Dynamic + ShardingSphere整合

一、多数据源如何整合

1、问题

直接引入dynamic-datasource-spring-boot-startershardingsphere-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());
        }
    }
}

二、多数据源整合

我们自己的数据源配置一定需要在DynamicDataSourceAutoConfigurationShardingSphereAutoConfiguration之前执行,手动接管数据源的创建过程,如果让默认的配置先跑,会造成数据源注册混乱。

主要配置两个方面,将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;
    }

}
相关推荐
辰海Coding12 小时前
MiniSpring框架学习-完成的 IoC 容器
java·spring boot·学习·架构
Maiko Star17 小时前
* SpringBoot整合LangChain4j
java·spring boot·后端·langchain4j
绝知此事18 小时前
【产品更名】通义灵码升级为 Qoder CN:AI 编码助手新时代,附大模型收费与 Spring Boot 支持全对比
人工智能·spring boot·后端·idea·ai编程
linmoo198618 小时前
Agent应用实践之四 - 基础:AgentScope-SpringBoot集成源码解析
人工智能·spring boot·agent·agentscope·openclaw
海兰19 小时前
【第21篇-续】graph-Stream-Node改造为适配openAI模型示例
java·人工智能·spring boot·spring·spring ai
Albert Edison20 小时前
基于 SpringBoot + RabbitMQ 完成企业级应用通信
spring boot·rabbitmq·java-rabbitmq
happymaker062621 小时前
Spring学习日记——DAY03(yml文件)
java·spring boot·spring
hikktn1 天前
企业级Spring Boot应用管理:从零打造生产级启动脚本
java·spring boot·后端
霸道流氓气质1 天前
Spring Boot + MyBatis-Plus 实现异常隔离的 Upsert 数据落库(含远程调用数据补全)
spring boot·后端·mybatis
不懂的浪漫1 天前
01|从 Spring Boot 项目理解 RAG:ingest、query、rerank、trace 到 eval
java·人工智能·spring boot·后端·ai·rag