Skip to content

sharding-jdbc使用

一、说明

这里有两个分支的包,主要介绍如下:

​ 在2016年开始、开源,当时它的名字还是Sharding JDBC。2017年发布了2.0版本,主打数据库治理功能;2018年正式更名为Sharding Sphere,而Sharding JDBC则演变为生态圈中的一部分,并且在同年开发了具有独立形态的Proxy代理,当年10月份通过Apache的投票,正式成为Apache孵化项目,也再次更名为Apache Sharding Sphere。大概一年半时间左右,成为Apache顶级项目,到了2021年迎来了5.0版本,从而开启一个新的机缘,提出了Databace Plus理念,是指构建数据库的上层标准。

注意:sharding jdbc支持读写分离,可实现一主多从,但不支持多主多从

参考官方文档的FAQ:https://shardingsphere.apache.org/document/current/cn/faq/

在3.x和4.x版本当中,如果只有部分数据库分库分表,需要将不分库分表的表也配置在分片规则

​ 因为ShardingSphere是将多个数据源合并为一个统一的逻辑数据源。因此即使不分库分表的部分,不配置分片规则ShardingSphere即无法精确的断定应该路由至哪个数据源。 但是ShardingSphere提供了两种变通的方式,有助于简化配置。

  1. 配置default-data-source,凡是在默认数据源中的表可以无需配置在分片规则中,ShardingSphere将在找不到分片数据源的情况下将表路由至默认数据源。
  2. 将不参与分库分表的数据源独立于ShardingSphere之外,在应用中使用多个数据源分别处理分片和不分片的情况。

在shardingjdbc5.x版本的官方文档FAQ中,关于不参与分库分表的说法,则是不需要配置,框架会自动识别,这样当然就简化了。

二、4.1.1版本

2.1 pom

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
		<!-- 对于springboot2.6及以上的版本需要加 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

2.2 配置文件

spring:
  application:
    name: sharding
  shardingsphere:
    # 是否打印sql
    props:
      sql:
        show: true
    datasource:
      # 有几个库
      names: db1,db2
      # 库1 的配置
      db1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/study_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
      # 库2 的配置
      db2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/study_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
    sharding:
      # 默认的库,其它数据可能会用到
      default-data-source-name: db1
      # 绑定的表 不配置也没找出啥问题
#      binding-tables: user
      # 配置表的分片规则
      tables:
        # 指定某个表的分片配置,这里是指的表名
        user:
          # 这个配置是告诉sharding有多少个库和多少个表
          actual-data-nodes: db$->{1..2}.user_$->{1..2}
          #分库策略
          database-strategy:
            # 行表达式模式
            inline:
              # 选择需要分库的字段,根据那个字段进行区分
              sharding-column: sex
              # 表达式,分库的算法,这个是通过年龄取模然后决定落到哪个库
              algorithm-expression: db$->{sex % 2 + 1}
          # 主键生成策略(如果是自动生成的,在插入数据的sql中就不要传id,null也不行,直接插入字段中就不要有主键的字段),如果是自定义的id,不需要自动生成,下面的配置不需要进行
          key-generator:
            # 对应的数据库表的主键
            column: id
            # 生成方式, 雪花模式
            type: SNOWFLAKE
          # 配置表分片策略
          table-strategy:
            # 行表达式
            inline:
              # 配置表分片的字段
              sharding-column: id
              # 配置表分片算法
              algorithm-expression: user_$->{id % 2 + 1 }
    props:
      # 日志显示具体的SQL
      sql:
      	show: true

2.3 配置类

springboot2.3健康检查配置:

@Configuration
public class DataSourceHealthConfig extends DataSourceHealthContributorAutoConfiguration {

    @Value("${spring.datasource.dbcp2.validation-query:select 1}")
    private String defaultQuery;

    public DataSourceHealthConfig(Map<String,DataSource> dataSources,ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders){
        super(dataSources,metadataProviders);
    }

    @Override
    protected AbstractHealthIndicator createIndicator(DataSource source){
        DataSourceHealthIndicator indicator = (DataSourceHealthIndicator)super.createIndicator(source);
        if (!StringUtils.hasText(indicator.getQuery())) {
            indicator.setQuery(defaultQuery);
        }
        return indicator;
    }
}

如果是mybayisplus比较低版本,不兼容LocalDate、LocalDateTime等jdk8日期,需要手动引入,否则报错。以LocalDateTime为例子:

public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {

    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void setNonNullParameter(PreparedStatement ps,int i,LocalDateTime parameter,JdbcType jdbcType)
    throws SQLException{
        if (parameter != null) {
            ps.setString(i,dateTimeFormatter.format(parameter));
        }
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs,String columnName) throws SQLException{
        String target = rs.getString(columnName);
        if (Tools.isEmpty(target)) {
            return null;
        }
        return LocalDateTime.parse(target,dateTimeFormatter);
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs,int columnIndex) throws SQLException{
        String target = rs.getString(columnIndex);
        if (Tools.isEmpty(target)) {
            return null;
        }
        return LocalDateTime.parse(target,dateTimeFormatter);
    }

    @Override
    public LocalDateTime getNullableResult(CallableStatement cs,int columnIndex) throws SQLException{
        String target = cs.getString(columnIndex);
        if (Tools.isEmpty(target)) {
            return null;
        }
        return LocalDateTime.parse(target,dateTimeFormatter);
    }
}

mybatisplus配置

@Configuration
public class MybatisPlusConfiguration {

    public MybatisPlusConfiguration(MybatisPlusProperties mybatisPlusProperties){
        MybatisConfiguration configuration = mybatisPlusProperties.getConfiguration();
        GlobalConfig globalConfig = mybatisPlusProperties.getGlobalConfig();
        GlobalConfigUtils.setGlobalConfig(configuration,globalConfig);
        configuration.getTypeHandlerRegistry().register(LocalDateTime.class,new LocalDateTimeTypeHandler());
        configuration.getTypeHandlerRegistry().register(LocalDate.class,new LocalDateTypeHandler());
        configuration.getTypeHandlerRegistry().register(LocalTime.class,new LocalTimeTypeHandler());
        GlobalConfigUtils.setGlobalConfig(configuration,globalConfig);
    }
}

三、5.2.1版本

3.1 pom

		<dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.2.1</version>
        </dependency>
        
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>
		<!-- 对于springboot2.3以上的版本需要加 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

3.2 配置文件

3.2.1 数据源配置

这里配置数据源,用于路由选择,注意配置names为名称,使用,隔开。下面对应的是数据源名称对应的配置

spring:
  shardingsphere:
    datasource:
      names: master-0,master-1,slave-0,slave-1
      master-0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/concurrency?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: root
        password: root
      master-1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3307/concurrency?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: root
        password: root
      slave-0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3308/concurrency?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: root
        password: root
      slave-1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3309/concurrency?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: root
        password: root

3.2.2 分库分表配置

这里主要介绍算法名称对应算法:

sharding-algorithm-name对应自定义算法名称。

算法type:

  • HASH_MOD 进行hash然后取模,后面必须跟sharding-count配置分片的大小
  • inline:通过algorithm-expression配置,使用groovy表达式,如 master-$->{id % 2},这里的参数必须要long或者integer
    rules:
      sharding:
      #默认数据源配置
        default-database-strategy:
          standard:
            sharding-column: id
            sharding-algorithm-name: default_inline
        tables:
          # 逻辑表的名称
          concurrency:
            # 数据节点配置,采用Groovy表达式
            actual-data-nodes: master-$->{0..1}.concurrency_$->{0..5}
            # 配置策略
            table-strategy:
              # 用于单分片键的标准分片场景
              standard:
                sharding-column: code
                # 分片算法名字
                sharding-algorithm-name: concurrency_inline
            database-strategy:
              # 用于单分片键的标准分片场景
              standard:
                sharding-column: code
                # 分片算法名字
                sharding-algorithm-name: database_inline
            key-generate-strategy: # 主键生成策略
              column: id  # 主键列
              key-generator-name: id-key  # 策略算法名称(推荐使用雪花算法)
        key-generators:
          id-key:
            type: SNOWFLAKE # 分布式序列算法类型
        sharding-algorithms:
          database_inline:
            type: HASH_MOD
            props:
              sharding-count: 2
          concurrency_inline:
            type: HASH_MOD
            props:
              sharding-count: 6
          default_inline:
            type: inline
            props:
              algorithm-expression: master-0
    props:
      # 日志显示具体的SQL
      sql-show: true

3.2.3 读写分离

因为sharding-jdbc只支持一主多从的读写分离,不支持多主的情况,所以下面的配置只拿单独的主从出来说

支持项

  • 提供了一主多从的读写分离配置,可独立使用,也可配合分库分表使用。
  • 同个调用线程,执行多条语句,其中一旦发现有非读操作,后续所有读操作均从主库读取。
  • Spring命名空间。
  • 基于Hint的强制主库路由。

不支持范围

  • 主库和从库的数据同步。
  • 主库和从库的数据同步延迟导致的数据不一致。
  • 主库双写或多写。
#读写分离配置
readwrite-splitting:
  data-sources:
    ds: # 逻辑数据源名字 不要乱写名字,否则读写分离不生效
      type: STATIC #静态类型,(动态Dynamic)
      props:
        # 主库
        write-data-source-name: master-0
        # 从库
        read-data-source-names: slave-0
        # 负载均衡算法名称
        load-balancer-name: round
      # 负载均衡算法
  load-balancers:
    round: # 负载均衡算法名称
      type: ROUND_ROBIN  #负载均衡算法类型轮询算法

四、异常解决

4.1 Compatible version of org.apache.shardingsphere.infra.util.yaml.constructor.ShardingSphereYamlConstructor$1

5.2 版本及以上会出现这个问题,因为需要的解析yaml文件的库版本不满足框架要求,pom文件中的 properties位置 调整yaml版本号即可(Springboot框架自带了这个框架,不需要重新引用)。

<properties>
    <snakeyaml.version>1.33</snakeyaml.version>
</properties>

如果上述配置不生效,使用下面的整体jar包配置

        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>

4.2 Database type inconsistent with...

所有数据源必须是相同的数据库类型;要么全是MySQL,要么全是Postgresql;否则抛出异常:Database type inconsistent with 'org.apache.shardingsphere.infra.database.type.dialect.MySQLDatabaseType@3083e6ef' and 'org.apache.shardingsphere.infra.database.type.dialect.PostgreSQLDatabaseType@6d57b264'

4.3 Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required

druid 要使用spring 版本而不是spring boot 版本,否则启动会报错

该问题其实是dataSource数据源冲突问题,sharding-jdbc会创建数据源,而druid也会创建数据源,所以导致冲突