springboot实现以代码的方式配置sharding-jdbc水平分表

目录
  • 关于依赖
    • shardingsphere-jdbc-core-spring-boot-starter
    • shardingsphere-jdbc-core
  • 数据源DataSource
    • 原DataSource
    • ShardingJdbcDataSource
    • 完整的ShardingJdbcDataSource配置
  • 分表策略
    • 主要的类
    • 其他的分表配置类
    • groovy行表达式说明
  • properties配置
  • Sharding-jdbc的坑
  • 结语

多数项目可能是已经运行了一段时间,才开始使用sharding-jdbc。

本教程就如何配置sharding-jdbc,才能使代码改动最少,对功能影响最少(如果已经做了垂直分表,只有一部分子项目需要水平分表)给出一个简单方案。

关于依赖

shardingsphere-jdbc-core-spring-boot-starter

官方给出了Spring Boot Starter配置

<dependency>
 <groupId>org.apache.shardingsphere</groupId>
 <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
 <version>${shardingsphere.version}</version>
</dependency>

但是基于已有项目,添加shardingsphere自动配置是很恶心的事

为什么配置了某个数据连接池的spring-boot-starter(比如druid)和 shardingsphere-jdbc-spring-boot-starter 时,系统启动会报错?

回答:

1. 因为数据连接池的starter(比如druid)可能会先加载并且其创建一个默认数据源,这将会使得 ShardingSphere‐JDBC 创建数据源时发生冲突。

2. 解决办法为,去掉数据连接池的starter 即可,sharing‐jdbc 自己会创建数据连接池。

一般项目已经有自己的DataSource了,如果使用shardingsphere-jdbc的自动配置,就必须舍弃原有的DataSource。

shardingsphere-jdbc-core

为了不放弃原有的DataSource配置,我们只引入shardingsphere-jdbc-core依赖

<dependency>
 <groupId>org.apache.shardingsphere</groupId>
 <artifactId>sharding-jdbc-core</artifactId>
 <version>4.1.1</version>
</dependency>

如果只水平分表,只支持mysql,可以排除一些无用的依赖

<dependency>
 <groupId>org.apache.shardingsphere</groupId>
 <artifactId>sharding-jdbc-core</artifactId>
 <version>4.1.1</version>
 <exclusions>
  <exclusion>
   <groupId>org.apache.shardingsphere</groupId>
   <artifactId>shardingsphere-sql-parser-postgresql</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>shardingsphere-sql-parser-oracle</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>shardingsphere-sql-parser-sqlserver</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>encrypt-core-rewrite</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>shadow-core-rewrite</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>encrypt-core-merge</artifactId>
        </exclusion>
        <exclusion>
         <!-- 数据库连接池,一般原有项目已引入其他的连接池 -->
         <groupId>com.zaxxer</groupId>
         <artifactId>HikariCP</artifactId>
        </exclusion>
        <exclusion>
         <!-- 也是数据库连接池,一般原有项目已引入其他的连接池 -->
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-dbcp2</artifactId>
        </exclusion>
        <exclusion>
         <!-- 对象池,可以不排除 -->
         <groupId>commons-pool</groupId>
         <artifactId>commons-pool</artifactId>
        </exclusion>
        <exclusion>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
        </exclusion>
        <exclusion>
         <!-- mysql驱动,原项目已引入,为了避免改变原有版本号,排除了吧 -->
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
        </exclusion>
        <exclusion>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
        </exclusion>
        <exclusion>
         <groupId>com.microsoft.sqlserver</groupId>
         <artifactId>mssql-jdbc</artifactId>
        </exclusion>
 </exclusions>
</dependency>

数据源DataSource

原DataSource

以Druid为例,原配置为

package com.xxx.common.autoConfiguration;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
/**
 * @ClassName: DruidConfiguration
 * @Description: Druid连接池配置
 */
@Configuration
@Slf4j
public class DruidConfiguration {
 @Value("${spring.datasource.driver-class-name}")
 private String driver;

 @Value("${spring.datasource.url}")
 private String url;

 @Value("${spring.datasource.username}")
 private String username;

 @Value("${spring.datasource.password}")
 private String password;
 @Value("${datasource.druid.initialsize}")
 private Integer druid_initialsize = 0;

 @Value("${datasource.druid.maxactive}")
 private Integer druid_maxactive = 20;

 @Value("${datasource.druid.minidle}")
 private Integer druid_minidle = 0;

 @Value("${datasource.druid.maxwait}")
 private Integer druid_maxwait = 30000;
 @Bean
    public ServletRegistrationBean druidServlet() {
     ServletRegistrationBean reg = new ServletRegistrationBean();
     reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", "root");
        reg.addInitParameter("loginPassword", "root!@#");
        //reg.addInitParameter("logSlowSql", "");
     return reg;
    }
    /**
     *
     * @Title: druidDataSource
     * @Description: 数据库源Bean
     * @param @return 参数说明
     * @return DataSource 返回类型
     * @throws
     */
    @Bean
    public DataSource druidDataSource() {
     // 数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver); // 驱动
        druidDataSource.setUrl(url); // 数据库连接地址
        druidDataSource.setUsername(username); // 数据库用户名
        druidDataSource.setPassword(password); // 数据库密码

        druidDataSource.setInitialSize(druid_initialsize);// 初始化连接大小
        druidDataSource.setMaxActive(druid_maxactive); // 连接池最大使用连接数量
        druidDataSource.setMinIdle(druid_minidle); // 连接池最小空闲
        druidDataSource.setMaxWait(druid_maxwait); // 获取连接最大等待时间

        // 打开PSCache,并且指定每个连接上PSCache的大小
        druidDataSource.setPoolPreparedStatements(false);
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(33);

        //druidDataSource.setValidationQuery("SELECT 1"); // 用来检测连接是否有效的sql
        druidDataSource.setTestOnBorrow(false); // 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
        druidDataSource.setTestOnReturn(false); // 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
        druidDataSource.setTestWhileIdle(false); // 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效

        druidDataSource.setTimeBetweenLogStatsMillis(60000); // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        druidDataSource.setMinEvictableIdleTimeMillis(1800000); // 配置一个连接在池中最小生存的时间,单位是毫秒
        // 当程序存在缺陷时,申请的连接忘记关闭,这时候,就存在连接泄漏
        // 配置removeAbandoned对性能会有一些影响,建议怀疑存在泄漏之后再打开。在上面的配置中,如果连接超过30分钟未关闭,就会被强行回收,并且日志记录连接申请时的调用堆栈。
        druidDataSource.setRemoveAbandoned(false); // 打开removeAbandoned功能
        druidDataSource.setRemoveAbandonedTimeout(1800); // 1800秒,也就是30分钟
        druidDataSource.setLogAbandoned(false); // 关闭abanded连接时输出错误日志

        // 过滤器
        List<Filter> filters = new ArrayList<Filter>();
        filters.add(this.getStatFilter()); // 监控
        //filters.add(this.getSlf4jLogFilter()); // 日志
        filters.add(this.getWallFilter()); // 防火墙
        druidDataSource.setProxyFilters(filters);
        log.info("连接池配置信息:"+druidDataSource.getUrl());
        return druidDataSource;
    }
 @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        WebStatFilter webStatFilter = new WebStatFilter();
        filterRegistrationBean.setFilter(webStatFilter);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
    /**
     *
     * @Title: getStatFilter
     * @Description: 监控过滤器
     * @param @return 参数说明
     * @return StatFilter 返回类型
     * @throws
     */
    public StatFilter getStatFilter(){
     StatFilter sFilter = new StatFilter();
     //sFilter.setSlowSqlMillis(2000); // 慢sql,毫秒时间
     sFilter.setLogSlowSql(false); // 慢sql日志
     sFilter.setMergeSql(true); // sql合并优化处理
     return sFilter;
    }

    /**
     *
     * @Title: getSlf4jLogFilter
     * @Description: 监控日志过滤器
     * @param @return 参数说明
     * @return Slf4jLogFilter 返回类型
     * @throws
     */
    public Slf4jLogFilter getSlf4jLogFilter(){
     Slf4jLogFilter slFilter =  new Slf4jLogFilter();
     slFilter.setResultSetLogEnabled(false);
     slFilter.setStatementExecutableSqlLogEnable(false);
     return slFilter;
    }
    /**
     *
     * @Title: getWallFilter
     * @Description: 防火墙过滤器
     * @param @return 参数说明
     * @return WallFilter 返回类型
     * @throws
     */
    public WallFilter getWallFilter(){
     WallFilter wFilter = new WallFilter();
     wFilter.setDbType("mysql");
     wFilter.setConfig(this.getWallConfig());
     wFilter.setLogViolation(true); // 对被认为是攻击的SQL进行LOG.error输出
     wFilter.setThrowException(true); // 对被认为是攻击的SQL抛出SQLExcepton
     return wFilter;
    }
    /**
     *
     * @Title: getWallConfig
     * @Description: 数据防火墙配置
     * @param @return 参数说明
     * @return WallConfig 返回类型
     * @throws
     */
    public WallConfig getWallConfig(){
     WallConfig wConfig = new WallConfig();
     wConfig.setDir("META-INF/druid/wall/mysql"); // 指定配置装载的目录
     // 拦截配置-语句
     wConfig.setTruncateAllow(false); // truncate语句是危险,缺省打开,若需要自行关闭
     wConfig.setCreateTableAllow(true); // 是否允许创建表
     wConfig.setAlterTableAllow(false); // 是否允许执行Alter Table语句
     wConfig.setDropTableAllow(false); // 是否允许修改表
     // 其他拦截配置
     wConfig.setStrictSyntaxCheck(true); // 是否进行严格的语法检测,Druid SQL Parser在某些场景不能覆盖所有的SQL语法,出现解析SQL出错,可以临时把这个选项设置为false,同时把SQL反馈给Druid的开发者
     wConfig.setConditionOpBitwseAllow(true); // 查询条件中是否允许有"&"、"~"、"|"、"^"运算符。
     wConfig.setMinusAllow(true); // 是否允许SELECT * FROM A MINUS SELECT * FROM B这样的语句
     wConfig.setIntersectAllow(true); // 是否允许SELECT * FROM A INTERSECT SELECT * FROM B这样的语句
     //wConfig.setMetadataAllow(false); // 是否允许调用Connection.getMetadata方法,这个方法调用会暴露数据库的表信息
     return wConfig;
    }
}

可见,如果用自动配置的方式放弃这些原有的配置风险有多大

怎么改呢?

ShardingJdbcDataSource

第一步,创建一个interface,用以加载自定义的分表策略

可以在各个子项目中创建bean,实现此接口

public interface ShardingRuleSupport {
 void configRule(ShardingRuleConfiguration shardingRuleConfig);
}

第二步,在DruidConfiguration.class中注入所有的ShardingRuleSupport

@Autowired(required = false)
private List<ShardingRuleSupport> shardingRuleSupport;

第三步,创建sharding-jdbc分表数据源

//包装Druid数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
//自定义一个名称为ds0的数据源名称,包装原有的Druid数据源,还可以再定义多个数据源
//因为只分表不分库,所有定义一个数据源就够了
dataSourceMap.put("ds0", druidDataSource);
//加载分表配置
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
//要加载所有的ShardingRuleSupport实现bean,所以用for循环加载
for (ShardingRuleSupport support : shardingRuleSupport) {
 support.configRule(shardingRuleConfig);
}
//加载其他配置
Properties properties = new Properties();
//由于未使用starter的自动装配,所以手动设置,是否显示分表sql
properties.put("sql.show", sqlShow);
//返回ShardingDataSource包装的数据源
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);

完整的ShardingJdbcDataSource配置

package com.xxx.common.autoConfiguration;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
/**
 * @ClassName: DruidConfiguration
 * @Description: Druid连接池配置
 */
@Configuration
@Slf4j
public class DruidConfiguration {
 @Value("${spring.datasource.driver-class-name}")
 private String driver;

 @Value("${spring.datasource.url}")
 private String url;

 @Value("${spring.datasource.username}")
 private String username;

 @Value("${spring.datasource.password}")
 private String password;
 @Value("${datasource.druid.initialsize}")
 private Integer druid_initialsize = 0;

 @Value("${datasource.druid.maxactive}")
 private Integer druid_maxactive = 20;

 @Value("${datasource.druid.minidle}")
 private Integer druid_minidle = 0;

 @Value("${datasource.druid.maxwait}")
 private Integer druid_maxwait = 30000;
 /**
  * 默认不显示分表SQL
  */
 @Value("${spring.shardingsphere.props.sql.show:false}")
 private boolean sqlShow;
 @Autowired(required = false)
 private List<ShardingRuleSupport> shardingRuleSupport;
 @Bean
    public ServletRegistrationBean druidServlet() {
     ServletRegistrationBean reg = new ServletRegistrationBean();
     reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", "root");
        reg.addInitParameter("loginPassword", "root!@#");
        //reg.addInitParameter("logSlowSql", "");
     return reg;
    }
    /**
     *
     * @Title: druidDataSource
     * @Description: 数据库源Bean
     * @param @return 参数说明
     * @return DataSource 返回类型
     * @throws
     */
    @Bean
    public DataSource druidDataSource() {
     // 数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver); // 驱动
        druidDataSource.setUrl(url); // 数据库连接地址
        druidDataSource.setUsername(username); // 数据库用户名
        druidDataSource.setPassword(password); // 数据库密码

        druidDataSource.setInitialSize(druid_initialsize);// 初始化连接大小
        druidDataSource.setMaxActive(druid_maxactive); // 连接池最大使用连接数量
        druidDataSource.setMinIdle(druid_minidle); // 连接池最小空闲
        druidDataSource.setMaxWait(druid_maxwait); // 获取连接最大等待时间

        // 打开PSCache,并且指定每个连接上PSCache的大小
        druidDataSource.setPoolPreparedStatements(false);
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(33);

        //druidDataSource.setValidationQuery("SELECT 1"); // 用来检测连接是否有效的sql
        druidDataSource.setTestOnBorrow(false); // 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
        druidDataSource.setTestOnReturn(false); // 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
        druidDataSource.setTestWhileIdle(false); // 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效

        druidDataSource.setTimeBetweenLogStatsMillis(60000); // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        druidDataSource.setMinEvictableIdleTimeMillis(1800000); // 配置一个连接在池中最小生存的时间,单位是毫秒
        // 当程序存在缺陷时,申请的连接忘记关闭,这时候,就存在连接泄漏
        // 配置removeAbandoned对性能会有一些影响,建议怀疑存在泄漏之后再打开。在上面的配置中,如果连接超过30分钟未关闭,就会被强行回收,并且日志记录连接申请时的调用堆栈。
        druidDataSource.setRemoveAbandoned(false); // 打开removeAbandoned功能
        druidDataSource.setRemoveAbandonedTimeout(1800); // 1800秒,也就是30分钟
        druidDataSource.setLogAbandoned(false); // 关闭abanded连接时输出错误日志

        // 过滤器
        List<Filter> filters = new ArrayList<Filter>();
        filters.add(this.getStatFilter()); // 监控
        //filters.add(this.getSlf4jLogFilter()); // 日志
        filters.add(this.getWallFilter()); // 防火墙
        druidDataSource.setProxyFilters(filters);
        log.info("连接池配置信息:"+druidDataSource.getUrl());
  if (shardingRuleSupport == null || shardingRuleSupport.isEmpty()) {
   log.info("............分表配置为空,使用默认的数据源............");
   return druidDataSource;
  }
  log.info("++++++++++++加载sharding jdbc配置++++++++++++");
        //包装Druid数据源
  Map<String, DataSource> dataSourceMap = new HashMap<>();
  //自定义一个名称为ds0的数据源名称,包装原有的Druid数据源,还可以再定义多个数据源
  //因为只分表不分库,所有定义一个数据源就够了
  dataSourceMap.put("ds0", druidDataSource);
  //加载分表配置
  ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
  //要加载所有的ShardingRuleSupport实现bean,所以用for循环加载
  for (ShardingRuleSupport support : shardingRuleSupport) {
   support.configRule(shardingRuleConfig);
  }
  //加载其他配置
  Properties properties = new Properties();
  //由于未使用starter的自动装配,所以手动设置,是否显示分表sql
  properties.put("sql.show", sqlShow);
  //返回ShardingDataSource包装的数据源
  return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
    }
 @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        WebStatFilter webStatFilter = new WebStatFilter();
        filterRegistrationBean.setFilter(webStatFilter);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

    /**
     *
     * @Title: getStatFilter
     * @Description: 监控过滤器
     * @param @return 参数说明
     * @return StatFilter 返回类型
     * @throws
     */
    public StatFilter getStatFilter(){
     StatFilter sFilter = new StatFilter();
     //sFilter.setSlowSqlMillis(2000); // 慢sql,毫秒时间
     sFilter.setLogSlowSql(false); // 慢sql日志
     sFilter.setMergeSql(true); // sql合并优化处理
     return sFilter;
    }

    /**
     *
     * @Title: getSlf4jLogFilter
     * @Description: 监控日志过滤器
     * @param @return 参数说明
     * @return Slf4jLogFilter 返回类型
     * @throws
     */
    public Slf4jLogFilter getSlf4jLogFilter(){
     Slf4jLogFilter slFilter =  new Slf4jLogFilter();
     slFilter.setResultSetLogEnabled(false);
     slFilter.setStatementExecutableSqlLogEnable(false);
     return slFilter;
    }
    /**
     *
     * @Title: getWallFilter
     * @Description: 防火墙过滤器
     * @param @return 参数说明
     * @return WallFilter 返回类型
     * @throws
     */
    public WallFilter getWallFilter(){
     WallFilter wFilter = new WallFilter();
     wFilter.setDbType("mysql");
     wFilter.setConfig(this.getWallConfig());
     wFilter.setLogViolation(true); // 对被认为是攻击的SQL进行LOG.error输出
     wFilter.setThrowException(true); // 对被认为是攻击的SQL抛出SQLExcepton
     return wFilter;
    }
    /**
     *
     * @Title: getWallConfig
     * @Description: 数据防火墙配置
     * @param @return 参数说明
     * @return WallConfig 返回类型
     * @throws
     */
    public WallConfig getWallConfig(){
     WallConfig wConfig = new WallConfig();
     wConfig.setDir("META-INF/druid/wall/mysql"); // 指定配置装载的目录
     // 拦截配置-语句
     wConfig.setTruncateAllow(false); // truncate语句是危险,缺省打开,若需要自行关闭
     wConfig.setCreateTableAllow(true); // 是否允许创建表
     wConfig.setAlterTableAllow(false); // 是否允许执行Alter Table语句
     wConfig.setDropTableAllow(false); // 是否允许修改表
     // 其他拦截配置
     wConfig.setStrictSyntaxCheck(true); // 是否进行严格的语法检测,Druid SQL Parser在某些场景不能覆盖所有的SQL语法,出现解析SQL出错,可以临时把这个选项设置为false,同时把SQL反馈给Druid的开发者
     wConfig.setConditionOpBitwseAllow(true); // 查询条件中是否允许有"&"、"~"、"|"、"^"运算符。
     wConfig.setMinusAllow(true); // 是否允许SELECT * FROM A MINUS SELECT * FROM B这样的语句
     wConfig.setIntersectAllow(true); // 是否允许SELECT * FROM A INTERSECT SELECT * FROM B这样的语句
     //wConfig.setMetadataAllow(false); // 是否允许调用Connection.getMetadata方法,这个方法调用会暴露数据库的表信息
     return wConfig;
    }
}

分表策略

主要的类

创建几个ShardingRuleSupport接口的实现Bean

@Component
public class DefaultShardingRuleAdapter implements ShardingRuleSupport {
 @Override
 public void configRule(ShardingRuleConfiguration shardingRuleConfiguration) {
  Collection<TableRuleConfiguration> tableRuleConfigs = shardingRuleConfiguration.getTableRuleConfigs();

  TableRuleConfiguration ruleConfig1 = new TableRuleConfiguration("table_one", "ds0.table_one_$->{0..9}");
  ComplexShardingStrategyConfiguration strategyConfig1 = new ComplexShardingStrategyConfiguration("column_id", new MyDefaultShardingAlgorithm());
  ruleConfig1.setTableShardingStrategyConfig(strategyConfig1);
  tableRuleConfigs.add(ruleConfig1);
  TableRuleConfiguration ruleConfig2 = new TableRuleConfiguration("table_two", "ds0.table_two_$->{0..9}");
  ComplexShardingStrategyConfiguration strategyConfig2 = new ComplexShardingStrategyConfiguration("column_id", new MyDefaultShardingAlgorithm());
  ruleConfig2.setTableShardingStrategyConfig(strategyConfig2);
  tableRuleConfigs.add(ruleConfig2);
 }
}
@Component
public class CustomShardingRuleAdapter implements ShardingRuleSupport {
 @Override
 public void configRule(ShardingRuleConfiguration shardingRuleConfiguration) {
  Collection<TableRuleConfiguration> tableRuleConfigs = shardingRuleConfiguration.getTableRuleConfigs();

  TableRuleConfiguration ruleConfig1 = new TableRuleConfiguration(MyCustomShardingUtil.LOGIC_TABLE_NAME, MyCustomShardingUtil.ACTUAL_DATA_NODES);
  ComplexShardingStrategyConfiguration strategyConfig1 = new ComplexShardingStrategyConfiguration(MyCustomShardingUtil.SHARDING_COLUMNS, new MyCustomShardingAlgorithm());
  ruleConfig1.setTableShardingStrategyConfig(strategyConfig1);
  tableRuleConfigs.add(ruleConfig1);
 }
}

其他的分表配置类

public class MyDefaultShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {
 public String getShardingKey () {
  return "column_id";
 }
 @Override
 public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
  Collection<String> col = new ArrayList<>();
  String logicTableName = shardingValue.getLogicTableName() + "_";
  Map<String, String> availableTargetNameMap = new HashMap<>();
  for (String targetName : availableTargetNameMap) {
   String endStr = StringUtils.substringAfter(targetName, logicTableName);
   availableTargetNameMap.put(endStr, targetName);
  }
  int size = availableTargetNames.size();

  //=,in
  Collection<String> shardingColumnValues = shardingValue.getColumnNameAndShardingValuesMap().get(this.getShardingKey());
  if (shardingColumnValues != null) {
   for (String shardingColumnValue : shardingColumnValues) {
    String modStr = Integer.toString(Math.abs(shardingColumnValue .hashCode()) % size);
    String actualTableName = availableTargetNameMap.get(modStr);
    if (StringUtils.isNotEmpty(actualTableName)) {
     col.add(actualTableName);
    }
   }
  }
  //between and
  //shardingValue.getColumnNameAndRangeValuesMap().get(this.getShardingKey());
  ... ...
  //如果分表列不是有序的,则between and无意义,没有必要实现
  return col;
 }
}
public class MyCustomShardingAlgorithm extends MyDefaultShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {
 @Override
 public String getShardingKey () {
  return MyCustomShardingUtil.SHARDING_COLUMNS;
 }
 @Override
 public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
  Collection<String> col = new ArrayList<>();
  String logicTableName = shardingValue.getLogicTableName() + "_";
  Map<String, String> availableTargetNameMap = new HashMap<>();
  for (String targetName : availableTargetNameMap) {
   String endStr = StringUtils.substringAfter(targetName, logicTableName);
   availableTargetNameMap.put(endStr, targetName);
  }
  Map<String, String> specialActualTableNameMap = MyCustomShardingUtil.getSpecialActualTableNameMap();
  int count = (int) specialActualTableNameMap.values().stream().distinct().count();
  int size = availableTargetNames.size() - count;

  //=,in
  Collection<String> shardingColumnValues = shardingValue.getColumnNameAndShardingValuesMap().get(this.getShardingKey());
  if (shardingColumnValues != null) {
   for (String shardingColumnValue : shardingColumnValues) {
    String specialActualTableName = specialActualTableNameMap.get(shardingColumnValue);
    if (StringUtils.isNotEmpty(specialActualTableName)) {
     col.add(specialActualTableName);
     continue;
    }
    String modStr = Integer.toString(Math.abs(shardingColumnValue .hashCode()) % size);
    String actualTableName = availableTargetNameMap.get(modStr);
    if (StringUtils.isNotEmpty(actualTableName)) {
     col.add(actualTableName);
    }
   }
  }
  //between and
  //shardingValue.getColumnNameAndRangeValuesMap().get(this.getShardingKey());
  ... ...
  //如果分表列不是有序的,则between and无意义,没有必要实现
  return col;
 }
}
@Component
public class MyCustomShardingUtil {
 /**
  * 逻辑表名
  */
 public static final String LOGIC_TABLE_NAME = "table_three";
 /**
  * 分片字段
  */
 public static final String SHARDING_COLUMNS = "column_name";
 /**
  * 添加指定分片表的后缀
  */
 private static final String[] SPECIAL_NODES = new String[]{"0sp", "1sp"};
 // ds0.table_three_$->{((0..9).collect{t -> t.toString()} << ['0sp','1sp']).flatten()}
 public static final String ACTUAL_DATA_NODES = "ds0." + LOGIC_TABLE_NAME + "_$->{((0..9).collect{t -> t.toString()} << "
             + "['" + SPECIAL_NODES[0] + "','" + SPECIAL_NODES[1] + "']"
             + ").flatten()}";
 private static final List<String> specialList0 = new ArrayList<>();
 @Value("${special.table_three.sp0.ids:null}")
 private void setSpecialList0(String ids) {
  if (StringUtils.isBlank(ids)) {
   return;
  }
  String[] idSplit = StringUtils.split(ids, ",");
  for (String id : idSplit) {
   String trimId = StringUtils.trim(id);
   if (StringUtils.isEmpty(trimId)) {
    continue;
   }
   specialList0.add(trimId);
  }
 }

 private static final List<String> specialList1 = new ArrayList<>();
 @Value("${special.table_three.sp1.ids:null}")
 private void setSpecialList1(String ids) {
  if (StringUtils.isBlank(ids)) {
   return;
  }
  String[] idSplit = StringUtils.split(ids, ",");
  for (String id : idSplit) {
   String trimId = StringUtils.trim(id);
   if (StringUtils.isEmpty(trimId)) {
    continue;
   }
   specialList1.add(trimId);
  }
 }
 private static class SpecialActualTableNameHolder {
  private static volatile Map<String, String> specialActualTableNameMap = new HashMap<>();
  static {
   for (String specialId : specialList0) {
    specialActualTableNameMap.put(specialId, LOGIC_TABLE_NAME + "_" + SPECIAL_NODES[0]);
   }
   for (String specialId : specialList1) {
    specialActualTableNameMap.put(specialId, LOGIC_TABLE_NAME + "_" + SPECIAL_NODES[1]);
   }
  }
 }
 /**
  * @return 指定ID的表名映射
  */
 public static Map<String, String> getSpecialActualTableNameMap() {
  return SpecialActualTableNameHolder.specialActualTableNameMap;
 }
}

ShardingAlgorithm接口的子接口除了ComplexKeysShardingAlgorithm,还有HintShardingAlgorithm,PreciseShardingAlgorithm,RangeShardingAlgorithm;本教程使用了更通用的ComplexKeysShardingAlgorithm接口。

配置TableRuleConfiguration类时,使用了两个参数的构造器

public TableRuleConfiguration(String logicTable, String actualDataNodes) {}

TableRuleConfiguration类还有一个参数的的构造器,没有实际数据节点,是给广播表用的

public TableRuleConfiguration(String logicTable) {}

groovy行表达式说明

ds0.table_three_$->{((0…9).collect{t -> t.toString()} << [‘0sp',‘1sp']).flatten()}

sharding-jdbc的groovy行表达式支持$->{…}或${…},为了避免与spring的占位符混淆,官方推荐使用$->{…}

(0..9) 获得0到9的集合

(0..9).collect{t -> t.toString()} 数值0到9的集合转换成字符串0到9的数组

(0..9).collect{t -> t.toString()} << ['0sp','1sp'] 字符串0到9的数组合并['0sp','1sp']数组,结果为 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ['0sp','1sp']]

flatten() 扁平化数组,结果为 ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0sp', '1sp']

properties配置

#是否显示分表SQL,默认为false
spring.shardingsphere.props.sql.show=true
#指定哪些列值入指定的分片表,多个列值以“,”分隔
#column_name为9997,9998,9999的记录存入表table_three_0sp中
#column_name为1111,2222,3333,4444,5555的记录存入表table_three_1sp中
#其余的值哈希取模后,存入对应的table_three_模数表中
special.table_three.sp0.ids=9997,9998,9999
special.table_three.sp1.ids=1111,2222,3333,4444,5555

Sharding-jdbc的坑

任何SQL,只要select子句中包含动态参数,则抛出类型强转异常

禁止修改分片键,如果update的set子句中存在分片键,则不能执行sql

结语

至此,简单的单表分表策略就配置完成了

代码没有好坏,合适的就是最好的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 使用sharding-jdbc实现水平分表的示例代码

    目录 在mysql中新建数据库sharding_db,新增两张结构一样的表student_1和student_2. 添加依赖 编写配置文件 编写实体类 编写mapper接口 编写测试类 执行测试 在mysql中新建数据库sharding_db,新增两张结构一样的表student_1和student_2. CREATE TABLE `student_1` ( `ID` bigint(20) NOT NULL , `NAME` varchar(50) CHARACTER SET utf8mb4 CO

  • SpringBoot整合sharding-jdbc实现分库分表与读写分离的示例

    目录 一.前言 二.数据库表准备 三.整合 四.docker-compose部署mysql主从 五.本文案例demo源码 一.前言 本文将基于以下环境整合sharding-jdbc实现分库分表与读写分离 springboot2.4.0 mybatis-plus3.4.3.1 mysql5.7主从 https://github.com/apache/shardingsphere 二.数据库表准备 温馨小提示:此sql执行时,如果之前有存在相应库和表会进行自动删除后再创建! DROP DATABAS

  • SpringBoot整合sharding-jdbc实现自定义分库分表的实践

    目录 一.前言 二.简介 1.分片键 2.分片算法 三.程序实现 一.前言 SpringBoot整合sharding-jdbc实现分库分表与读写分离 本文将通过自定义算法来实现定制化的分库分表来扩展相应业务 二.简介 1.分片键 用于数据库/表拆分的关键字段 ex: 用户表根据user_id取模拆分到不同的数据库中 2.分片算法 可参考:https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere

  • springboot实现以代码的方式配置sharding-jdbc水平分表

    目录 关于依赖 shardingsphere-jdbc-core-spring-boot-starter shardingsphere-jdbc-core 数据源DataSource 原DataSource ShardingJdbcDataSource 完整的ShardingJdbcDataSource配置 分表策略 主要的类 其他的分表配置类 groovy行表达式说明 properties配置 Sharding-jdbc的坑 结语 多数项目可能是已经运行了一段时间,才开始使用sharding-

  • 使用sharding-jdbc实现水平分库+水平分表的示例代码

    前面的文章使用sharding-jdbc实现水平分表中详细记录了如何使用sharding-jdbc实现水平分表,即根据相应的策略,将一部分数据存入到表1中,一部分数据存入到表2中,逻辑上为同一张表,分表操作全部交由sharding-jdbc进行处理. 可能根据需要,还需要将一张表的数据拆分存入到多个数据库中,甚至多个数据库的多个表中,使用sharding-jdbc同样可以实现. 重复的篇幅则不再赘述,下面重点记录升级的过程. 分库分表策略:将id为偶数的存入到库1中,奇数存入到库2中,在每个库中

  • springboot+mybatis拦截器方法实现水平分表操作

    目录 1.前言 2.MyBatis 允许使用插件来拦截的方法 3.Interceptor接口 4分表实现 4.1.大体思路 4.2.1 Mybatis如何找到我们新增的拦截服务 4.2.2 应该拦截什么样的对象 4.2.3 实现自定义拦截器 4.2.逐步实现 1.前言 业务飞速发展导致了数据规模的急速膨胀,单机数据库已经无法适应互联网业务的发展.由于MySQL采用 B+树索引,数据量超过阈值时,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降:高并发访问请求也使得集中式数

  • SpringBoot集成Sharding Jdbc使用复合分片的实践

    目录 1.Sharing JDBC 简介 2.系统改造 2.1 对接外部系统的系统 2.2 内部系统间的调用 3.解决方案 4.代码实现 4.1 Sharding JDBC 配置 4.2 数据源操作类 4.3 分片测试类 4.4 测试结果 参考文章: 最近主要的工作重心是数据库的容量规划. 随着业务的逐渐增大,原有保存在单表的数据量也日益增强.数据库数据会随着业务的发展而不断增多,因此数据操作,如增删改查的开销也会越来越大.再加上物理服务器的资源有限(CPU.磁盘.内存.IO 等).最终数据库所

  • spring boot使用sharding jdbc的配置方式

    本文介绍了spring boot使用sharding jdbc的配置方式,分享给大家,具体如下: 说明 要排除DataSourceAutoConfiguration,否则多数据源无法配置 @SpringBootApplication @EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) public class Application { public static void main(String[] arg

  • springboot+quartz以持久化的方式实现定时任务的代码

    这篇文章给大家介绍springboot+quartz以持久化的方式实现定时任务,详情如下所示: 篇幅较长,耐心的人总能得到最后的答案小生第一次用quartz做定时任务,不足之处多多谅解. 首先 在springboot项目里做定时任务是比较简单的,最简单的实现方式是使用**@Scheduled注解,然后在application启动类上使用@EnableScheduling**开启定时任务. 示例 @SpringBootApplication @EnableScheduling public cla

  • Springboot自带定时任务实现动态配置Cron参数方式

    目录 Springboot自带定时任务实现动态配置Cron参数 SpringBoot定时任务的四种实现方式(主要) spring动态配置cron表达式,不需要停服 SchedulingConfigurer接口实现动态加载cron表达式 Springboot自带定时任务实现动态配置Cron参数 同学们,我今天分享一下SpringBoot动态配置Cron参数.场景是这样子的:后台管理界面对定时任务进行管理,可动态修改执行时间,然后保存入库,每次任务执行前从库里查询时间,以达到动态修改Cron参数的效

  • springboot项目访问静态资源的配置代码实例

    这篇文章主要介绍了springboot项目访问静态资源的配置代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 这里只是简单记录当上传图片不是放在tomcat其他服务器中时,只是放在磁盘中便可以这样配置,在项目启动后可以访问到磁盘中的资源. @Configuration public class SystemConfigurer implements WebMvcConfigurer { @Value("${jeewx.path.uploa

  • springboot aop里的@Pointcut()的配置方式

    目录 springboot aop里的@Pointcut()的配置 springboot aop @Pointcut的用法 springboot aop里的@Pointcut()的配置 @Pointcut("execution(public * com.wangzhou.newboot.exception.TestExceptionController.test(String,String))") com.wangzhou.newboot.exception是TestException

随机推荐