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;
    }

}
相关推荐
北风toto3 小时前
Spring Boot / Spring Cloud 配置文件加密详解:使用 jasypt-spring-boot 实现 ENC() 加密
spring boot·后端·spring cloud
工作log3 小时前
Spring Boot 3.5 + MyBatis Plus + RabbitMQ:打造 AI 驱动的慢 SQL 监控与优化系统
spring boot·mybatis·java-rabbitmq
苍煜4 小时前
Java自定义注解-SpringBoot实战
java·开发语言·spring boot
计算机学姐4 小时前
基于微信小程序的校园失物招领管理系统【uniapp+springboot+vue】
java·vue.js·spring boot·mysql·信息可视化·微信小程序·uni-app
直奔標竿5 小时前
Java开发者AI转型第二十课!Spring AI MCP 双向实战:客户端与服务端手把手落地
java·开发语言·人工智能·spring boot·后端·spring
敖正炀5 小时前
Spring 深度内核-Spring Boot 内核与自动配置-Spring Boot 启动流程:SpringApplication.run() 全解
spring boot
Knight_AL6 小时前
使用 CyclicBarrier + 自定义线程池实现 SpringBoot 并行报表(完整性能对比)
java·spring boot·后端
计算机学姐7 小时前
基于微信小程序的宠物服务系统【uniapp+springboot+vue】
java·vue.js·spring boot·mysql·微信小程序·uni-app·宠物
Knight_AL7 小时前
从 0 到 1:PG WAL → Debezium → Kafka → Spring Boot → Redis
spring boot·redis·kafka