SpringBoot 集成 Sharding-JDBC 实现读写分离、分库分表

文章目录

一、Sharding-JDBC的应用场景

Sharding-JDBC是针对分库分表后的操作简化,相当于增强版的JDBC驱动,常被用于实现应用层读写分离以及分库分表,具体概括可以查看官网这里不做过多说明。

官方文档

二、SpringBoot 集成 Sharding-JDBC

2.1、前期准备

要想比较好的体验Sharding-JDBC能力最好使用一主多从数据库模式,我这里会使用MySQL一主两从,如果嫌比较麻烦也可以直接使用一主也行,区别不是很大,再执行日志中都能看出区别。

想要部署MySQL一主两从可以参考:Linux从零部署MySQL8.0 主从复制(一主两从)

  • 创建一个测试库sharding-jdbc-test然后新增一张测试表后续测试都会使用
sql 复制代码
CREATE TABLE `t_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
  • 代码结构(这里会使用到Mybatis-plus方便进行测试)

2.2、导入pom.xml依赖包

我这里使用SpringBoot2.x sharding-jdbc4.x 不同大版本可能会有冲突,如果测试时发现有冲突先检测一下代码是否有问题,如果代码没有问题可以调整一下包的版本。

xml 复制代码
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
    </parent>

    <properties>
        <hutool.version>5.2.5</hutool.version>
        <fastjson.version>1.2.74</fastjson.version>
        <lombok.version>1.18.12</lombok.version>
        <common.version>1.0-SNAPSHOT</common.version>
        <commons-lang3.version>3.10</commons-lang3.version>

        <druid.version>1.1.10</druid.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
        <sharding-jdbc.version>4.0.0</sharding-jdbc.version>
    </properties>

    <dependencies>
        <!--工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--Mysql依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
        <!--druid数据源驱动 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

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

2.3、结构代码实现

2.3.1、MybatisPlusConfig(分页插件)
java 复制代码
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
2.3.2、TOrder(订单对象)
java 复制代码
@Data
public class TOrder implements Serializable {
    private static final long serialVersionUID = 1L;

    /** 订单id */
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    /** 订单id (通过雪花算法生成)*/
    private Long orderId;

    /** 订单编号 */
    private String orderNo;

    /** 用户id */
    private Integer userId;

    /** 商品信息 */
    private String goodsInfo;

    /** 收件地址 */
    private String toAddress;

    /** 创建时间 */
    private Date createTime;
}
2.3.3、TOrderMapper(订单mapper接口)
java 复制代码
public interface TOrderMapper extends BaseMapper<TOrder> {
    @Select("SELECT t1.*,t2.`name` FROM t_order t1 LEFT JOIN t_user t2 ON t1.user_id = t2.id;")
    List<Map<String,Object>> queryOrderList();
}
  • ITOrderService(订单接口)
java 复制代码
public interface ITOrderService extends IService<TOrder> {
	void updateOrder(Long id, String toAddress);
}
2.3.4、TOrderServiceImpl(订单接口实现)
java 复制代码
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOrder(Long id, String toAddress){
        // 强制路由主库
        // HintManager.getInstance().setMasterRouteOnly();
        TOrder order = this.getById(id);
        if(order == null){
        	System.out.println("订单不存在");
            return;
        }
        this.lambdaUpdate().eq(TOrder::getId, id).set(TOrder::getToAddress, toAddress).update();

        order = this.getById(id);
    }
}
2.3.5、ShardingApplication(启动类)
java 复制代码
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
@MapperScan("com.kerwin.service.mapper")
public class ShardingApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingApplication.class);
    }
}
2.3.6、application.yml(这个配置文件中只配置公用信息,具体分库分表信息通过active指定)
yaml 复制代码
server:
  port: 9090

spring:
  profiles:
    active: duxiefenli
#    active: fenbiao-qumo
#    active: duxiefenli-fenbiao-qumo
#    active: fenkufenbiao-qumo
#    active: zidingyi-fenbiao-date
  main:
    allow-bean-definition-overriding: true
  datasource:
    druid:
      validation-query: SELECT 1  #验证数据库服务可用性的查询SQL,一般设置为SELECT 1
      initial-size: 10 #初始连接 默认0
      max-active: 20 #最大连接数 默认8
      min-idle: 10 # 最小连接数 默认0
      max-wait: 5000 # 获取连接最大等待时间,单位毫秒 默认-1一直等待

logging:
  level:
    com.baomidou: debug
    com.kerwin: debug

mybatis-plus:
  configuration:
    #开启详细日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # Mybatis 一级缓存,默认为 SESSION 开启一级缓存,STATEMENT 关闭一级缓存
    # SESSION session 级别缓存,同一个 session 相同查询语句不会再次查询数据库
    localCacheScope: STATEMENT
    # Mybatis 二级缓存,默认为 true
    cacheEnabled: false
2.3.7、SpringBoot测试类(用于测试所有应用场景,内容后面补充)
java 复制代码
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingJDBCTest {
    @Autowired
    private ITOrderService orderService;

    @Autowired
    private TOrderMapper orderMapper;
}

三、Sharding-JDBC常用场景

Sharding-JDBC有多种使用场景,可以通过配置文件灵活配置读写分离、分库分表等,这里会对一下常用方式做配置说明。

3.1、读写分离

3.1.1、准备读写分离配置文件 application-duxiefenli.yml
yaml 复制代码
# 分表配置
spring:
  shardingsphere:
    datasource:
      # 全部数据源名称 多个用逗号隔开
      names:
        master1,slave1,slave2
      # 主数据源
      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
      # 从数据源
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.191:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
      slave2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.192:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456

    masterslave:
      # 读写分离配置 用于配置从库负载均衡算法类型,可选值:ROUND_ROBIN(轮询),RANDOM(随机)
      load-balance-algorithm-type: round_robin
      # 最终的数据源名称
      name: dataSource
      # 主库数据源名称
      master-data-source-name: master1
      # 从库数据源名称列表,多个逗号分隔
      slave-data-source-names: slave1,slave2
    props:
      # 开启SQL显示,默认false
      sql:
        show: true
3.1.2、测试代码
java 复制代码
    //----------------------------- 读写分离测试 start -----------------------------
    @Test
    public void duxiefenliTest(){
        // 1、测试自动主库写操作(在执行写操作时会自动选择主库)
        for (int i = 0; i < 10; i++) {
            TOrder tOrder = new TOrder();
            tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));
            tOrder.setUserId(RandomUtil.randomInt(10));
            tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));
            tOrder.setToAddress("地址"+RandomUtil.randomString(5));
            tOrder.setCreateTime(new Date());
            orderService.save(tOrder);
        }
        // 2、测试从库自动轮询读操作
        TOrder tOrder1 = orderService.getById(1);
        TOrder tOrder2 = orderService.getById(2);
    }
    //----------------------------- 读写分离测试 end -----------------------------
3.1.3、测试读写分离效果

这里直接运行测试类中的读写分离测试方法,运行之后拉到最下面可以看到插入数据时使用的数据源是DataSources: master1,而查询时使用的数据源是DataSources: slave1 DataSources: slave2,多次查看可以看到会对两个从数据源进行轮询。

3.2、读写分离强制路由主库

3.2.1、准备读写分离配置文件 application-duxiefenli.yml

配置文件和读写分离配置文件一致

3.2.2、测试代码
java 复制代码
    //----------------------------- 读写分离强制路由主库测试 start -----------------------------
    @Test
    public void duxiefenliMasterRouteTest(){
        // 测试一个事务中有写也有读操作数据源路由情况
        orderService.updateOrder(1L,"罗马");
    }
    //----------------------------- 读写分离强制路由主库测试 end -----------------------------
3.1.3、测试未开启强制路由主库同一个事务中 查询->更新->查询

这里可以看到第一次查询路由到了从库,第二次查询路由到了主库,在同一个事务中如果进行了修改操作那么后面都会路由主库。

3.1.4、测试开启强制路由主库同一个事务中 查询->更新->查询

打开强制路由主库HintManager.getInstance().setMasterRouteOnly();运行后可以看到第一次查询也是使用的主库

3.3、分表(根据user_id取模分表)

3.3.1、准备分片表

这里准备两个分片表,t_order_0 t_order_1

sql 复制代码
CREATE TABLE `t_order_0` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_1` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
3.3.2、准备分表配置文件 application-fenbiao-qumo.yml
yaml 复制代码
# 取模分表配置
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      # 全部数据源名称 多个用逗号隔开
      names:
        master1
      # 主数据源
      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
    props:
      # 开启SQL显示,默认false
      sql:
        show: true
    # 分表配置
    sharding:
      tables:
        t_order:
          # 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
          # ds0代表数据源名称,t_order表名称 ,$->{0..1} inline表达式代表取值0 1
          actual-data-nodes: master1.t_order_$->{0..1}
          table-strategy:
            # 分表策略为inline
            inline:
              # 分表列名
              sharding-column: user_id
              # 分表算法 根据t_order表中的user_id取模2进行数据分片存储
              algorithm-expression: t_order_$->{user_id % 2}
3.3.3、测试代码
java 复制代码
    //----------------------------- 分表测试 start -----------------------------
    @Test
    public void fenbiaoTest(){
        // 1、测试自动主库写操作(在执行写操作时会自动选择主库)
        for (int i = 0; i < 10; i++) {
            TOrder tOrder = new TOrder();
            tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));
            tOrder.setUserId(RandomUtil.randomInt(10));
            tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));
            tOrder.setToAddress("地址"+RandomUtil.randomString(5));
            tOrder.setCreateTime(new Date());
            orderService.save(tOrder);
        }
        // 2、测试根据userId分表查询
        List<TOrder> list = orderService.lambdaQuery().eq(TOrder::getUserId, 7).list();
    }
    //----------------------------- 分表测试 end -----------------------------
3.3.4、测试分表效果

先将application.yml里的active配置成fenbiao-qumo,这里可以观察到新增和查询都能根据user_id自动取模匹配对应表

3.4、分表 & 配置读写分离

这里只有配置文件不同,其它逻辑和分表一致,只用将application.yml里的active配置成duxiefenli-fenbiao-qumo

3.4.1、准备分表 & 配置读写分离配置文件 application-duxiefenli-fenbiao-qumo.yml
yaml 复制代码
# 读写分离+取模分表
spring:
  main:
    allow-bean-definition-overriding: true
  datasource:
    druid:
      access-to-underlying-connection-allowed: true
  shardingsphere:
    datasource:
      # 全部数据源名称 多个用逗号隔开
      names:
        master1,slave1,slave2
      # 主数据源
      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
      # 从数据源
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.191:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
      slave2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.192:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
    props:
      # 开启SQL显示,默认false
      sql:
        show: true

    sharding:
      # 读写分离配置
      master-slave-rules:
        ds0:
          master-data-source-name: master1
          slave-data-source-names: slave1, slave2
          # 负载均衡算法  ROUND_ROBIN:轮询  RANDOM:随机
          load-balance-algorithm-type: ROUND_ROBIN
      # 分表配置
      tables:
        t_order:
          # 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
          # ds0代表数据源名称,t_order表名称 ,$->{0..1} inline表达式代表取值0 1
          actual-data-nodes: ds0.t_order_$->{0..1}
          table-strategy:
            # 分表策略为inline
            inline:
              # 分表列名
              sharding-column: user_id
              # 分表算法 根据t_order表中的user_id取模2进行数据分片存储
              algorithm-expression: t_order_$->{user_id % 2}

3.5、分库分表

3.5.1、准备两个库在每个库中怎么两张表

我这里安装了两MySQL,在两个MySQL中都创建一个叫sharding-jdbc-test的库,然后在库中创建两个分片表,t_order_0 t_order_1,也可以在一个MySQL中创建两个数据库测试效果是一样的。

sql 复制代码
CREATE TABLE `t_order_0` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_1` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
3.5.2、准备分库分表配置文件 application-fenkufenbiao-qumo.yml
yaml 复制代码
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      # 全部数据源名称 多个用逗号隔开
      names:
        master0,master1
      # 主数据源 数据源名称不能有下划线可以用 -
      master0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.182:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
    props:
      # 开启SQL显示,默认false
      sql:
        show: true

    sharding:
      # 分库配置
      default-database-strategy:
        inline:
          # 分库依据字段
          sharding-column: user_id
          # 分库规则
          algorithm-expression: master$->{user_id % 2}
      # 分表配置
      tables:
        t_order:
          # 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
          # master代表数据源名称, $->{0..1} inline表达式代表取值0 1,user_info表名称 ,$->{0..1} inline表达式代表取值0 1
          actual-data-nodes: master$->{0..1}.t_order_$->{0..1}
          table-strategy:
            # 分表策略为inline
            inline:
              # 分表列名
              sharding-column: order_id
              # 分表算法 根据t_order表中的order_id取模2进行数据分片存储
              algorithm-expression: t_order_$->{order_id % 2}
          # 自定义t_order中order_id生成,使用雪花算法
          key-generator:
            column: order_id
            type: SNOWFLAKE
            props:
              worker:
                # 雪花算法的workId  机器为标识 0-1024
                id: 996
3.5.3、测试代码
java 复制代码
    //----------------------------- 分库分表测试 start -----------------------------
    @Test
    public void fenkuFenbiaoTest(){
        // 1、测试自动主库写操作(在执行写操作时会自动选择主库)
        for (int i = 0; i < 10; i++) {
            TOrder tOrder = new TOrder();
            tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));
            tOrder.setUserId(RandomUtil.randomInt(10));
            tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));
            tOrder.setToAddress("地址"+RandomUtil.randomString(5));
            tOrder.setCreateTime(new Date());
            orderService.save(tOrder);
        }
        // 2、测试根据userId分表查询
//        List<TOrder> list = orderService.lambdaQuery().eq(TOrder::getUserId, 7).list();
    }
    //----------------------------- 分表测试 end -----------------------------
3.5.4、测试分库分表效果

先将application.yml里的active配置成fenkufenbiao-qumo,执行后可以看到根据我们配置的规则进行了分库分表。

3.6、自定义分表策略(通过时间分表)

Sharding-JDBC提供了表达式配置分表,同时也提供了自定义分表策略方式,不同业务的分表方案不同,常见的有取模、根据时间分表,这里会进行自定义时间分表策略演示,每个月分一张表。

3.6.1、准备分片表

这里准备两张表t_order_2024_7 t_order_2024_8,用来演示自定义分表策略。

sql 复制代码
CREATE TABLE `t_order_2024_7` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_2024_8` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',
  `order_no` varchar(100) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',
  `to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
);
3.6.2、自定义分表策略代码实现

分表策略有两个,精确分片PreciseShardingAlgorithm,范围区间分片RangeShardingAlgorithm,这里会对订单创建时间进行两种分片策略实现。

3.6.2.1、精确分片策略实现
java 复制代码
@Slf4j
public class OrderCreateTimePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
        System.out.println("table PreciseShardingAlgorithm ");
        // 真实节点
        availableTargetNames.stream().forEach((item) -> {
            log.info("actual node table:{}", item);
        });

        log.info("logic table name:{},rout column:{}", shardingValue.getLogicTableName(), shardingValue.getColumnName());
        //精确分片
        log.info("column value:{}", shardingValue.getValue());
        
        String tb_name = shardingValue.getLogicTableName() + "_";

        // 根据当前日期 来 分库分表
        Date date = shardingValue.getValue();
        String year = String.format("%tY", date);
        String mon =String.valueOf(Integer.parseInt(String.format("%tm", date))); // 去掉前缀0

        // 选择表
        tb_name = tb_name + year + "_" + mon;
        System.out.println("tb_name:" + tb_name);

        for (String each : availableTargetNames) {
            System.out.println("t_order_:" + each);
            if (each.equals(tb_name)) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }
}
3.6.2.2、范围区间分片策略实现
java 复制代码
@Slf4j
public class OrderCreateTimeRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {

        ArrayList<String> result = new ArrayList<>();

        //获取表名称
        String tb_name = rangeShardingValue.getLogicTableName() + "_";

        //获取查询时间
        Range<Date> valueRange = rangeShardingValue.getValueRange();
        Integer queryYearMonthBegin = null; // 查询开始年月
        if(valueRange.hasLowerBound()){
            Date beginDate = valueRange.lowerEndpoint();
            queryYearMonthBegin = Integer.valueOf(String.format("%tY", beginDate)+String.format("%tm", beginDate));
        }
        Integer queryYearMonthEnd = null; // 查询结束年月
        if(valueRange.hasUpperBound()){
            Date endDate = valueRange.upperEndpoint();
            queryYearMonthEnd = Integer.valueOf(String.format("%tY", endDate)+String.format("%tm", endDate));
        }


        //筛选需要查询的表
        for (String each : collection) {
            // 将表中的年月取出来用于判断
            String yearMonth = each.replace(tb_name, "");
            String[] yearMonthSplit = yearMonth.split("_");
            String year = yearMonthSplit[0];
            String mon = yearMonthSplit[1];
            mon = mon.length()==1?"0"+mon:mon;
            Integer yearMonthInt = Integer.parseInt(year+mon); // 数据表对应年月
            // 当范围查询时为封闭区间 且表的年月在查询区间内
            if(valueRange.hasLowerBound() && valueRange.hasUpperBound()){
                if(yearMonthInt>=queryYearMonthBegin && yearMonthInt<=queryYearMonthEnd){
                    result.add(each);
                }
            }
            // 当范围查询时为封闭区间
            if(valueRange.hasLowerBound() && !valueRange.hasUpperBound()){
                if(yearMonthInt>=queryYearMonthBegin){
                    result.add(each);
                }
            }
            // 当范围查询时为封闭区间
            if(!valueRange.hasLowerBound() && valueRange.hasUpperBound()){
                if(yearMonthInt<=queryYearMonthEnd){
                    result.add(each);
                }
            }
        }

        if(result.size()==0){
            log.error("查询表不存在");
            throw new IllegalArgumentException();
        }
        return result;
    }
}
3.6.3、准备自定义分表配置文件 application-zidingyi-fenbiao-date.yml
java 复制代码
# 自定义时间分表配置
# 自定义时间分表配置
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      # 全部数据源名称 多个用逗号隔开
      names:
        master0
      # 主数据源
      master0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
    props:
      # 开启SQL显示,默认false
      sql:
        show: true
      #工作线程数量,默认值: CPU核数
      executor:
        size: 6
    # 分表配置
    sharding:
      tables:
        t_order:
          # 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
          # master代表数据源名称,t_order表名称 ,$->{7..8} inline表达式代表取值7 8
          actual-data-nodes: master0.t_order_$->{2024..2024}_$->{7..8}
          table-strategy:
            # 分表策略为 自定义策略
            standard:
              # 分表列名
              sharding-column: create_time
              # 自定义精确分表算法类路径
              precise-algorithm-class-name: com.kerwin.config.OrderCreateTimePreciseShardingAlgorithm
              # 自定义范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
              range-algorithm-class-name: com.kerwin.config.OrderCreateTimeRangeShardingAlgorithm
          # 自定义t_order中order_id生成,使用雪花算法
          key-generator:
            column: order_id
            type: SNOWFLAKE
            props:
              worker:
                # 雪花算法的workId  机器为标识 0-1024
                id: 996
3.6.4、测试代码
java 复制代码
    //----------------------------- 自定义分表策略测试 start -----------------------------
    @Test
    public void zidinyiFenbiaoTest(){
        // 1、插入数据
        for (int i = 0; i < 10; i++) {
            TOrder tOrder = new TOrder();
            tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));
            tOrder.setUserId(RandomUtil.randomInt(10));
            tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));
            tOrder.setToAddress("地址"+RandomUtil.randomString(5));
            tOrder.setCreateTime(new Date());
            orderService.save(tOrder);
        }
//        // 2、根据时间精确查询
//        List<TOrder> list = orderService.lambdaQuery()
//                .eq(TOrder::getCreateTime, DateUtil.parseDate("2024-08-07 00:00:00"))
//                .list();
//
//        // 3、根据时间范围查询
//        List<TOrder> list1 = orderService.lambdaQuery()
//                .ge(TOrder::getCreateTime, DateUtil.parseDate("2024-07-01 00:00:00"))
//                .le(TOrder::getCreateTime, DateUtil.parseDate("2024-08-30 23:59:59"))
//                .list();
    }
    //----------------------------- 自定义分表策略测试 end -----------------------------
3.6.4、测试自定义分表策略效果
3.6.4.1、插入数据查看分片表选择

执行插入数据可以观察到选择到了t_order_2024_8表。

3.6.4.2、根据时间精确查询查看分片表选择

根据时间精确查询可以观察到选择到了t_order_2024_8表。

3.6.4.2、根据时间范围查询查看分片表选择

根据时间范围查询可以观察到选择到了t_order_2024_7 t_order_2024_8表。

四、总结

这里只演示了部分情况,还有像多表关联查询、分页查询等都是可以适配的,在分页查询中有个问题,如果在分库或分表的情况下因为数据在不同表中获取结果集是有问题的,因为普通的分页查询没法确定多张表的顺序问题,比如一个表有两个分片,查询第3页每页3条数据,如果没有做分表那么在数据库一共会扫描9条数据取出第7-9条,而在分表的情况下则不能只取出每个分片中的第7-9条数据,必须将1-9条数据都取出来进行应用层归并处理,Sharding-JDBC已经将这个功能实现并且进行了优化,虽然做了优化但是不可避免分页查询还是存在的性能问题,要想解决这个问题也有很多方法这里不做展开。

相关推荐
Q_19284999061 分钟前
基于Spring Boot的个性化推荐外卖点餐系统
java·spring boot·后端
字节程序员2 分钟前
接口自动化测试实战经验分享,测试用例也能自动生成
数据库·测试用例·压力测试
J不A秃V头A9 分钟前
Spring Boot开发编译后读取不到@spring.profiles.active@的问题
spring boot
今天不coding11 分钟前
MySQL体系架构
数据库·mysql·体系架构·网络连接层·服务层·存储引擎层·系统文件层
会说法语的猪14 分钟前
IDEA使用Alt + Enter快捷键自动接受返回值一直有final修饰的问题处理
java·ide·intellij-idea
喝醉酒的小白22 分钟前
PostgreSQL: 事务年龄
数据库·postgresql
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ23 分钟前
MyBatis执行完sql后,返回的数值代表的意思
java·开发语言
码到成龚25 分钟前
SQL server学习10-数据库编程(中)
数据库·学习
wlyang66625 分钟前
1. SQL常见笔试题目
数据库·sql
smilejingwei29 分钟前
SQL,生成指定时间间隔内的事件次序号
数据库·sql·spl·esprocspl