Springboot2.X 集成 Sharding-JDBC3.X

以前的项目中集成了springboot2.x + dangdang sharding-jdbc 1.5.4.1
pom依赖java

<dependency>
			<groupId>com.dangdang</groupId>
			<artifactId>sharding-jdbc-core</artifactId>
			<version>1.5.4.1</version>
		</dependency>

具体的设置参考博客 : 数据库分库分表sharding-jdbc 这里不作过多的赘述mysql

咱们知道sharding-jdbc 目前已经改名为 shardingsphere,而上面的依赖最后一次更新是在2017年,比较的老旧,也不支持where条件下的 OR 查询等, 无奈之下只好从新更换新的依赖,git

<dependency>
			<groupId>io.shardingsphere</groupId>
			<artifactId>sharding-jdbc-core</artifactId>
			<version>3.1.0</version>
		</dependency>

sharding-sphere规则信息和配置信息 参考这个博客github

shardingsphere 分片具体实现以下(可参考官网文档):web

// 配置真实数据源
    Map<String, DataSource> dataSourceMap = new HashMap<>();
    
    // 配置第一个数据源
    BasicDataSource dataSource1 = new BasicDataSource();
    dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource1.setUrl("jdbc:mysql://localhost:3306/ds0");
    dataSource1.setUsername("root");
    dataSource1.setPassword("");
    dataSourceMap.put("ds0", dataSource1);
    
    // 配置第二个数据源
    BasicDataSource dataSource2 = new BasicDataSource();
    dataSource2.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource2.setUrl("jdbc:mysql://localhost:3306/ds1");
    dataSource2.setUsername("root");
    dataSource2.setPassword("");
    dataSourceMap.put("ds1", dataSource2);
    
    // 配置Order表规则
    TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
    orderTableRuleConfig.setLogicTable("t_order");
    orderTableRuleConfig.setActualDataNodes("ds${0..1}.t_order${0..1}");
    
    // 配置分库 + 分表策略
    orderTableRuleConfig.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}"));
    orderTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order${order_id % 2}"));
    
    // 配置分片规则
    ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
    shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
    
    // 省略配置order_item表规则...
    // ...
    
    // 获取数据源对象
    DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new ConcurrentHashMap(), new Properties());

到这里简单的数据源配置就ok了,可是有时候咱们事先并不知道数据源的具体地址,这些是在某些xml中动态配置的,并且可能咱们仅仅有个别的表须要分库分表,其余的表又有可能分布在不一样的数据节点上;
好比: (手动画图,凑合看吧)
在这里插入图片描述
如上图情景,咱们须要动态的配置各自表所在的数据源,spring

思路:sql

  1. 从配置文件中读取全部的数据源信息,设置别名
    2.配置数据表与别名的关联关系
    3.不一样的分库分表策略须要配置各自的ruleConfig
    4.动态配置 actualDataNodes,根据本身的需求自行拼接

直接上代码吧,数据库

@Configuration
public class DynamicDataSourceConfigV2 {

    private static final Logger logger = LogManager.getLogger(DynamicDataSourceConfigV2.class);

    /** 目前只有报警表须要分库分表 */
    private final static String LOGIC_TABLE = "alarms";

    /** 目前的报警分为16张表,后缀以下 */
    private final static char[] tables_suffix = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

    private List<DataSource> allDataSource = new LinkedList<>();

    private String defaultDsAlias;

    /** * alarm表进行了分库操做,因此它对应的有多个库, * 其它的表只有一个库, List中只有一个数据源(别名). */
    private HashMap<String, List<String>> dsMappingTableName = new HashMap<>();

    /** * 此项就是本集群全部数据源的对应关系 * 数据源别名对应数据源 * String : 别名(ds0,ds1...) * DataSource: 数据源 */
    private HashMap<String,DataSource> aliasDs = new HashMap<>();


    final ClusterConfig clusterConfig;

    @Value("${local.test}")
    private boolean localTest;

    @Autowired
    public DynamicDataSourceConfigV2(ClusterConfig clusterConfig){
        this.clusterConfig = clusterConfig;
        logger.info("初始化 DynamicDataSourceConfigV2 ");
        List<MysqlService> allMysqlInfo = clusterConfig.currentCluster().getMysql();
        int ds = 0;
        String alias;
        for (MysqlService mysql : allMysqlInfo) {
            String url = mysql.getUrl();
            String username = mysql.getUsername();
            String password = mysql.getPassword();
            logger.fatal("main mysql url: {}, username: {}, password: {}",
                    url, username, password);
            DataSource dataSource = commonDataSource(url,username,password);
            // 当前数据源别名
            alias = "ds"+ds;
            aliasDs.put(alias,dataSource);
            // 以前cluster.xml中的默认数据库的配置依然有效
            if(mysql.isDefault() || this.defaultDsAlias == null){
                this.defaultDsAlias = alias;
            }
            allDataSource.add(dataSource);
            List<String> tables = mysql.getTables();
            if (tables != null) {
                for (String tableName : tables) {
                    List<String> list = dsMappingTableName.get(tableName);
                    if (list == null) {
                        list = new ArrayList<>();
                        if (StringUtils.isNotBlank(tableName)) {
                            this.dsMappingTableName.put(tableName, list);
                        }
                    }
                    list.add(alias);
                }
            } else {
                logger.warn("not set tables");
            }
            ds++;
        }
    }

    @Bean("shardingDataSource")
    public DataSource shardingDataSource() throws SQLException {
        // mybatis 的相关日志开启debug
        if(localTest){
            logger.info("mybatis 开启debug模式");
            LogFactory.useStdOutLogging();
        }
        return buildDataSourceV2();
    }

    /** * 须要手动配置事务管理器 * @param shardingDataSource * @return */
    @Bean
    public DataSourceTransactionManager transactitonManager(@Qualifier("shardingDataSource") DataSource shardingDataSource){
        logger.info("须要手动配置事务管理器");
        return new DataSourceTransactionManager(shardingDataSource);
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("shardingDataSource") DataSource shardingDataSource) throws Exception {
        logger.info("须要手动配置SqlSessionFactory");
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(shardingDataSource);
        return bean.getObject();
    }

    private DataSource buildDataSourceV2() throws SQLException {
        Set<String> keys = dsMappingTableName.keySet();
        Collection<TableRuleConfiguration> tableRuleConfigurations = new LinkedList<>();
        // 遍历全部cluster.xml 中配置的表
        for(String tableName:keys){
            // 获取该表对应的数据源别名集
            List<String> list = getDataSource(tableName);
            TableRuleConfiguration tableRuleConfiguration;
            // 拼接actualDataNodes 注意去掉最后一个','字符
            StringBuilder builder = new StringBuilder();
            for(String alis:list){
                if(LOGIC_TABLE.equals(tableName)){
                    for(char ch :tables_suffix){
                        builder.append(alis).append(".").append(tableName).append("_").append(ch).append(",");
                    }
                }else{
                    builder.append(alis).append(".").append(tableName).append(",");
                }
            }
            // 移除最后一个字符
            int index = builder.length() - 1;
            builder = builder.deleteCharAt(index);

            tableRuleConfiguration = LOGIC_TABLE.equals(tableName) ? getAlarmTableRuleConfiguration(LOGIC_TABLE,builder.toString())
                    : getOtherTableRuleConfiguration(tableName,builder.toString());
            logger.info("tableName:{},builder:{}",tableName,builder.toString());
            tableRuleConfigurations.add(tableRuleConfiguration);
        }

        // 分片规则配置初始化
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        // 设置默认数据库(未能命中下面定制的规则时会使用该数据源)
        shardingRuleConfig.setDefaultDataSourceName(defaultDsAlias);

        Properties properties = new Properties();
        // 开启sharding-jdbc的sql显示功能,默认是关闭的
        properties.setProperty("sql.show",Boolean.TRUE.toString());

        shardingRuleConfig.getTableRuleConfigs().addAll(tableRuleConfigurations);
        DataSource dataSource = ShardingDataSourceFactory.createDataSource(aliasDs,shardingRuleConfig,
                new ConcurrentHashMap(),properties);
        return dataSource;
    }

    /** * 报警表的分表分库策略 * @param logicTable * @param ActualDataNodes 格式:db_${0..1}.gps_${0..1} 至关于 db_0.gps_0,db_0.gps_1,db_1.gps_0,db_1.gps_1 * @return */
    private TableRuleConfiguration getAlarmTableRuleConfiguration(String logicTable,String ActualDataNodes) {
        TableRuleConfiguration result = new TableRuleConfiguration();
        // 设置分表的基础表(前缀部分)
        result.setLogicTable(logicTable);
        // 数据节点
        result.setActualDataNodes(ActualDataNodes);
        // 分库策略
        result.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("device_id",
                new ModuleDatabaseShardingAlgorithmV2()));

        // 分表策略
        result.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("device_id",
                new ModuleTableShardingAlgorithmV2(),new ModuleTableRangeShardingAlgorithmV2()));

        return result;
    }

    /** * 其余表的分表分库策略 * @return */
    private TableRuleConfiguration getOtherTableRuleConfiguration(String logicTable,String ActualDataNodes) {
        TableRuleConfiguration result = new TableRuleConfiguration();
        // 设置分表的基础表(前缀部分)
        result.setLogicTable(logicTable);
        // 数据节点
        result.setActualDataNodes(ActualDataNodes);
        return result;
    }

    /** * 根据tableName 获取数据源别名 * @param tableName * @return */
    public List<String> getDataSource(String tableName) {
        logger.info("get DataSource by table name: {}", tableName);
        List<String> list;
        if(StringUtils.isBlank(tableName)){
            logger.info("not found table : {}, use default", tableName);
            list =  new ArrayList<>(1);
            list.add(defaultDsAlias);
        }else{
            list = dsMappingTableName.get(tableName);
            if(list == null){
                logger.info("not found session facotry, {}", tableName);
                list =  new ArrayList<>(1);
                list.add(defaultDsAlias);
            }
        }
        return list;
    }

    /** * 获取全部的报警分表 * @param logicTable * @return */
    private List<String> listAlarmTableNames(String logicTable){
        List<String> tableNames = new ArrayList<>(tables_suffix.length);
        for(char suffix :tables_suffix){
            StringBuilder builder = new StringBuilder();
            builder.append(logicTable).append("_").append(suffix);
            tableNames.add(builder.toString());
        }
        return tableNames;
    }

    private DataSource commonDataSource(String url, String username, String password){
        DataSourceBuilder builder = DataSourceBuilder.create();
        builder.driverClassName(com.mysql.jdbc.Driver.class.getName());
        builder.url(url);
        builder.username(username);
        builder.password(password);
        return builder.build();
    }

}
这里有个坑须要注意一下,有关KeyGeneratorColumnName强制要求是Number类型,若是咱们但愿字段是String等类型时,不要指定有关KeyGenerator的东西,好比:

tableRuleConfiguration.setKeyGeneratorColumnName("xxx")
tableRuleConfiguration.setKeyGenerator(xxx)

不然会报错,血泪史。。。

完事,若有疑问,欢迎沟通apache