dynamic-datasource + sharding 动态刷新表

本文介绍如何结合dynamic-datasource和Sharding-JDBC实现动态数据源切换及按年分表策略。通过Spring Boot整合,实现多数据源管理和动态刷新表结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求

在系统中可能会遇到 动态数据源 以及分表分库,动态分表

的情况,动态数据源切换 采用的

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
   <version>3.2.1</version>
</dependency>

分表分库采用

<dependency>
   <groupId>org.apache.shardingsphere</groupId>
   <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
   <version>4.1.1</version>
</dependency>
<!-- for spring namespace -->
<dependency>
   <groupId>org.apache.shardingsphere</groupId>
   <artifactId>sharding-jdbc-spring-namespace</artifactId>
   <version>4.1.1</version>
</dependency>

将表shardingsphere 交给DynamicDataSource管理

package com.iot.cloud.iotdevice.config;

/**
 * @author lj
 * @title: DataSourceConfiguration
 * @projectName iot-platform-cloud
 * @description: TODO  管理多数据源 shardingsphere的数据源交给dynamic-datasource去维护。
 * @date 2022/2/17 001713:52
 */

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;


@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfiguration {

    /**
     * 动态数据源配置项
     */
    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    @Value("${spring.shardingsphere.datasource.names:}")
    private String SHARDING_DATA_SOURCE_NAME;
    
    @Lazy
    @Resource
    ShardingDataSource shardingDataSource;

    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = dynamicDataSourceProperties.getDatasource();
        return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);

                // 将 shardingjdbc 管理的数据源也交给动态数据源管理
                dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource);
                return dataSourceMap;
            }
        };
    }
    @Primary
    @Bean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
        dataSource.setStrict(dynamicDataSourceProperties.getStrict());
        dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
        dataSource.setSeata(dynamicDataSourceProperties.getSeata());
        return dataSource;
    }
}
 

特别注意   这里的DataSource  指定  ShardingDataSource  不然后面刷新Sharding动态表时会转换失败。

动态刷新表

package com.iot.cloud.iotdevice.init;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.iot.cloud.iotdevice.config.TableNamesConfig;
import com.iot.cloud.iotdevice.constant.DataSourceConstant;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.javassist.Modifier;
import org.apache.shardingsphere.core.rule.TableRule;
import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
import org.apache.shardingsphere.shardingjdbc.jdbc.unsupported.AbstractUnsupportedOperationDataSource;
import org.apache.shardingsphere.underlying.common.config.exception.ShardingSphereConfigurationException;
import org.apache.shardingsphere.underlying.common.rule.DataNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Administrator
 * @title: lj
 * @projectName citymanagementplatform
 * @description: TODO
 * @date 2021/1/12 00129:41
 */
@Log4j2
@Component
@EnableConfigurationProperties(TableNamesConfig.class)
public class DeviceCreateTableHandler {

    @Autowired
    private TableNamesConfig tableNamesConfig;

    @Value("${spring.shardingsphere.datasource.names:}")
    private String SHARDING_DATA_SOURCE_NAME;

    @Autowired
    private DataSource dataSource;


    /***
     * 启动的时候初始化一下 动态表单的配置    初化当前年
     */
    @PostConstruct
    private void intData() {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        String s = format.format(date);
        actualTablesRefresh(Integer.parseInt(s));
    }

    /**
     * 初始化下一天的
     */
    @Scheduled(cron = "0 0 20 31 12 ?")
    public void nextInit() {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        String s = format.format(date);
        actualTablesRefresh(Integer.parseInt(s) + 1);
    }

    /**
     * 动态更新 处理化的表配置
     *
     * @param data 表名称
     */
    public void actualTablesRefresh(Integer data) {
        try {
            DynamicRoutingDataSource dd =(DynamicRoutingDataSource) this.dataSource;
            Map<String, DataSource> map = dd.getCurrentDataSources();
            if (tableNamesConfig.getNames() == null || tableNamesConfig.getNames().length == 0) {
                log.info("dynamic.table.names为空");
                return;
            }
            for (int i = 0; i < tableNamesConfig.getNames().length; i++) {
                // 这里 前缀为 数据库简称  后面为表名称
                String dbName = tableNamesConfig.getNames()[i];
                String [] dbNames = dbName.split("-");
                DataSource dsource =  map.get(dbNames[0]);
                ShardingDataSource dataSource = (ShardingDataSource) dsource;
                TableRule tableRule = null;
                try {
                    tableRule = dataSource.getRuntimeContext().getRule().getTableRule(dbNames[1]);
                } catch (ShardingSphereConfigurationException e) {
                    log.error("逻辑表:{},不存在配置!", dbNames[1]);
                }
                // 动态刷新 actualDataNodes
                List<DataNode> dataNodes = tableRule.getActualDataNodes();
                Field actualDataNodesField = TableRule.class.getDeclaredField("actualDataNodes");
                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(actualDataNodesField, actualDataNodesField.getModifiers() & ~Modifier.FINAL);
                actualDataNodesField.setAccessible(true);
                List<DataNode> newDataNodes = new ArrayList<>();
                int time = tableNamesConfig.getStartYear();
                String dataSourceName = dataNodes.get(0).getDataSourceName();
                while (true) {
                    DataNode dataNode = new DataNode(dataSourceName + "." + dbNames[1] + "_" + time);
                    newDataNodes.add(dataNode);
                    time = time + 1;
                    if (time > data.intValue()) {
                        break;
                    }
                }
                actualDataNodesField.set(tableRule, newDataNodes);
                Set<String> actualTables = Sets.newHashSet();
                Map<DataNode, Integer> dataNodeIndexMap = Maps.newHashMap();
                AtomicInteger index = new AtomicInteger(0);
                newDataNodes.forEach(dataNode -> {
                    actualTables.add(dataNode.getTableName());
                    if (index.intValue() == 0) {
                        dataNodeIndexMap.put(dataNode, 0);
                    } else {
                        dataNodeIndexMap.put(dataNode, index.intValue());
                    }
                    index.incrementAndGet();
                });
                // 动态刷新 actualTablesField
                Field actualTablesField = TableRule.class.getDeclaredField("actualTables");
                actualTablesField.setAccessible(true);
                actualTablesField.set(tableRule, actualTables);
                // 动态刷新 dataNodeIndexMapField
                Field dataNodeIndexMapField = TableRule.class.getDeclaredField("dataNodeIndexMap");
                dataNodeIndexMapField.setAccessible(true);
                dataNodeIndexMapField.set(tableRule, dataNodeIndexMap);
                // 动态刷新 datasourceToTablesMapField
                Map<String, Collection<String>> datasourceToTablesMap = Maps.newHashMap();
                datasourceToTablesMap.put(dataSourceName, actualTables);
                Field datasourceToTablesMapField = TableRule.class.getDeclaredField("datasourceToTablesMap");
                datasourceToTablesMapField.setAccessible(true);
                datasourceToTablesMapField.set(tableRule, datasourceToTablesMap);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("初始化 动态表单失败" + e.getMessage());
        }
    }
}
 

特别注意 我这里是按年分表 ,具体的分表策略你们自己安需求弄。

配置 yml

spring:
  main:
    allow-bean-definition-overriding: true
  ######## 配置多数据源 ########
  datasource:
    dynamic:
      #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: true
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:${DATASOURCE_DBTYPE:mysql}://${DATASOURCE_HOST:192.168.0.252}:${DATASOURCE_PORT:3306}/iot_platform?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
          username: ${DATASOURCE_USERNAME:root}
          password: ${DATASOURCE_PASSWORD:123456}
  ###   这里采用的按年分表策略  对拉取的公厕硬件设备数据进行分表 暂时不分库 #####
  shardingsphere:
    props:
      sql:
        show: true
    datasource:
      names: toilet
      ###  主数据库 ###
      toilet:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:${DATASOURCE_DBTYPE:mysql}://${DATASOURCE_HOST:192.168.0.252}:${DATASOURCE_PORT:3306}/iot_toilet?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
        username: ${DATASOURCE_USERNAME:root}
        password: ${DATASOURCE_PASSWORD:123456}
    sharding:
      tables:
        wc_passenger:
          ### 数据表 按年分表 ####   流量
          actual-data-nodes: toilet.wc_passenger
          table-strategy:
            standard:
              sharding-column: created_time
              precise-algorithm-class-name: xxx
              range-algorithm-class-name: xxxx

### dynamic-datasourceSharding 的集成及最佳实践 #### 背景介绍 `dynamic-datasource` 是一款用于 Spring Boot 应用程序的多数据源解决方案,能够轻松实现动态切换数据源的功能。而 `ShardingSphere` 则是一个分布式数据库中间件,提供了分库分、读写分离等功能[^1]。两者的结合可以满足复杂的业务场景需求。 --- #### 技术架构概述 为了实现 `dynamic-datasource` 和 `ShardingSphere` 的集成,通常采用以下方式: 1. **主数据源配置** 使用 `dynamic-datasource` 定义多个数据源,并指定其中一个作为默认数据源。通过 `@Primary` 注解标记,默认情况下应用程序会优先使用该数据源。 2. **动态数据源注册** 将 `ShardingSphere` 管理的数据源作为一个独立的数据源注册到 `dynamic-datasource` 中。这样可以在运行时根据 SQL 特性选择合适的数据源执行查询操作。 3. **事务管理** 当涉及跨数据源的操作时,可以通过 Seata 或其他分布式事务框架来保障一致性[^5]。 4. **代码层面的支持** 在实际开发中,可能需要手动编写逻辑以控制数据源的选择行为。例如,在某些特定条件下强制切换至某个数据源。 --- #### 配置步骤详解 以下是基于官方文档推荐的最佳实践配置流程: ##### 1. 添加依赖项 在项目的 Maven 构建文件中添加必要的依赖包: ```xml <dependencies> <!-- dynamic-datasource --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!-- ShardingSphere-JDBC --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.1</version> </dependency> </dependencies> ``` ##### 2. 数据源定义 创建自定义类完成数据源初始化工作: ```java @Configuration public class DataSourceConfig { @Bean public DataSource dataSource(DynamicDataSourceProvider provider) { DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource(); // 设置主数据源名称 routingDataSource.setPrimary("master"); // 是否启用严格模式 routingDataSource.setStrict(false); // 加载动态数据源提供者 routingDataSource.setProvider(provider); return routingDataSource; } } ``` ##### 3. 动态数据源提供者 实现接口 `DynamicDataSourceProvider` 并重写其方法返回可用的数据源集合: ```java @Component public class CustomDynamicDataSourceProvider implements DynamicDataSourceProvider { private final Map<String, DataSource> dataSources; public CustomDynamicDataSourceProvider() throws SQLException { this.dataSources = new HashMap<>(); // 初始化 master 数据源 DruidDataSource masterDs = new DruidDataSource(); masterDs.setUrl("jdbc:mysql://localhost:3306/master_db"); masterDs.setUsername("root"); masterDs.setPassword("password"); dataSources.put("master", masterDs); // 初始化 slave 数据源 (可选) DruidDataSource slaveDs = new DruidDataSource(); slaveDs.setUrl("jdbc:mysql://localhost:3306/slave_db"); slaveDs.setUsername("slave_user"); slaveDs.setPassword("slave_password"); dataSources.put("slave", slaveDs); } @Override public Collection<DataSource> loadDataSources() { return dataSources.values(); } } ``` ##### 4. ShardingSphere 数据源集成 如果希望利用 `ShardingSphere` 提供的分片功能,则需额外声明对应的规则并将其嵌入到现有体系之中: ```yaml spring: shardingsphere: datasource: names: ds_0,ds_1 ds_0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds_0?serverTimezone=UTC&useSSL=false username: root password: password ds_1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds_1?serverTimezone=UTC&useSSL=false username: root password: password rules: sharding: tables: t_order: actual-data-nodes: ds_${0..1}.t_order_${0..1} table-strategy: standard: sharding-column: order_id precise-algorithm-class-name: org.example.algorithm.PreciseModuloAlgorithm key-generate-strategy: column: order_id key-generator-class-name: org.apache.shardingsphere.core.keygen.DefaultKeyGenerator ``` 注意:上述 YAML 文件中的 `${}` 达式示占位符替换机制,具体数值范围取决于真实环境下的分区数量[^4]。 --- #### 关键点分析 - **性能优化建议** - 对于高并发场景下频繁访问的小型,考虑缓存化处理减少 IO 开销。 - 如果存在大量只读请求,可通过增加副本节点缓解压力[^3]。 - **错误排查技巧** 若遇到类似 `"Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"` 错误提示,可能是由于未正确定义 Mapper 接口所致,请确认 XML 映射文件路径已正确导入[^2]。 - **安全性考量** 敏感字段存储前应先经过加密算法转换再入库;同时限制外部网络直连权限以防泄露风险[^9]。 ---
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

carry杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值