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

news2025/4/12 0:21:17

文章目录

    • 一、Sharding-JDBC的应用场景
    • 二、SpringBoot 集成 Sharding-JDBC
      • 2.1、前期准备
      • 2.2、导入`pom.xml`依赖包
      • 2.3、结构代码实现
        • 2.3.1、MybatisPlusConfig(分页插件)
        • 2.3.2、TOrder(订单对象)
        • 2.3.3、TOrderMapper(订单mapper接口)
        • 2.3.4、TOrderServiceImpl(订单接口实现)
        • 2.3.5、ShardingApplication(启动类)
        • 2.3.6、application.yml(这个配置文件中只配置公用信息,具体分库分表信息通过active指定)
        • 2.3.7、SpringBoot测试类(用于测试所有应用场景,内容后面补充)
    • 三、Sharding-JDBC常用场景
      • 3.1、读写分离
        • 3.1.1、准备读写分离配置文件 application-duxiefenli.yml
        • 3.1.2、测试代码
        • 3.1.3、测试读写分离效果
      • 3.2、读写分离强制路由主库
        • 3.2.1、准备读写分离配置文件 application-duxiefenli.yml
        • 3.2.2、测试代码
        • 3.1.3、测试未开启强制路由主库同一个事务中 查询->更新->查询
        • 3.1.4、测试开启强制路由主库同一个事务中 查询->更新->查询
      • 3.3、分表(根据user_id取模分表)
        • 3.3.1、准备分片表
        • 3.3.2、准备分表配置文件 application-fenbiao-qumo.yml
        • 3.3.3、测试代码
        • 3.3.4、测试分表效果
      • 3.4、分表 & 配置读写分离
        • 3.4.1、准备分表 & 配置读写分离配置文件 application-duxiefenli-fenbiao-qumo.yml
      • 3.5、分库分表
        • 3.5.1、准备两个库在每个库中怎么两张表
        • 3.5.2、准备分库分表配置文件 application-fenkufenbiao-qumo.yml
        • 3.5.3、测试代码
        • 3.5.4、测试分库分表效果
      • 3.6、自定义分表策略(通过时间分表)
        • 3.6.1、准备分片表
        • 3.6.2、自定义分表策略代码实现
          • 3.6.2.1、精确分片策略实现
          • 3.6.2.2、范围区间分片策略实现
        • 3.6.3、准备自定义分表配置文件 application-zidingyi-fenbiao-date.yml
        • 3.6.4、测试代码
        • 3.6.4、测试自定义分表策略效果
          • 3.6.4.1、插入数据查看分片表选择
          • 3.6.4.2、根据时间精确查询查看分片表选择
          • 3.6.4.2、根据时间范围查询查看分片表选择
    • 四、总结

一、Sharding-JDBC的应用场景

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

二、SpringBoot 集成 Sharding-JDBC

2.1、前期准备

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

  • 创建一个测试库sharding-jdbc-test然后新增一张测试表后续测试都会使用
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 不同大版本可能会有冲突,如果测试时发现有冲突先检测一下代码是否有问题,如果代码没有问题可以调整一下包的版本。

    <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(分页插件)
@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(订单对象)
@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接口)
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(订单接口)
public interface ITOrderService extends IService<TOrder> {
	void updateOrder(Long id, String toAddress);
}
2.3.4、TOrderServiceImpl(订单接口实现)
@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(启动类)
@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指定)
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测试类(用于测试所有应用场景,内容后面补充)
@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
# 分表配置
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、测试代码
    //----------------------------- 读写分离测试 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、测试代码
    //----------------------------- 读写分离强制路由主库测试 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

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
# 取模分表配置
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、测试代码
    //----------------------------- 分表测试 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
# 读写分离+取模分表
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中创建两个数据库测试效果是一样的。

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
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、测试代码
    //----------------------------- 分库分表测试 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,用来演示自定义分表策略。

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、精确分片策略实现
@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、范围区间分片策略实现
@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
# 自定义时间分表配置
# 自定义时间分表配置
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、测试代码
    //----------------------------- 自定义分表策略测试 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已经将这个功能实现并且进行了优化,虽然做了优化但是不可避免分页查询还是存在的性能问题,要想解决这个问题也有很多方法这里不做展开。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读】Fourier Spectrum Discrepancies in Deep Network Generated Images

文章目录 Learning Self-Consistency for Deepfake Detection背景关键方法傅立叶谱分析图像转换分类实验讨论总结Learning Self-Consistency for Deepfake Detection 会议:NeurIPS 2020 作者: 背景 深度生成模型(GAN、VAE等)能生成与真图无法区分的逼真图像 关键 对…

批发行业进销存-库存预警 源码CyberWinApp-SAAS 本地化及未来之窗行业应用跨平台架构

一交互代码 未来之窗_人工智能_VOS通用(数据,库存预警,1080,500);"> 二、通用代码 function 未来之窗_人工智能_VOS通用(网址,标题,宽度,高度){var 机器人默认page网址;var 未来之窗app_通用ID"未来之窗防重复";var title标题;var 机器人宽度宽度;var 机器…

Tensorflow预训练模型转PyTorch

深度学习领域是计算机科学中变化最快的领域之一。大约 5 年前&#xff0c;当我开始研究这个主题时&#xff0c;TensorFlow 被认为是主导框架。如今&#xff0c;大多数研究人员已经转向 PyTorch。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/…

Opencv学习-窗口交互

交互操作能够增加用户对程序流程的控制&#xff0c;使程序可以根据用户需求实现不同的处理结果。有时某一个参数需要反复尝试不同的数值&#xff0c;这时交互操作可以实现在程序运行过程中改变参数数值的作用&#xff0c;避免重复运行程序&#xff0c;节省时间&#xff0c;同时…

AI智能名片小程序源码在付费媒体与企业营销策略中的创新应用

摘要&#xff1a;在数字化浪潮的推动下&#xff0c;付费媒体已成为企业营销战略的核心组成部分。从视频流媒体平台到社交媒体&#xff0c;企业需不断探索新颖且高效的营销手段以吸引目标受众并促进业务增长。AI智能名片小程序源码&#xff0c;作为融合人工智能、小程序技术与传…

什么是实时数据仓库?它有哪些不可替代之处?

【实时数据仓库】可以分开来理解&#xff1a; ✅【实时数据】&#xff1a;即能够快速处理数据&#xff0c;且几乎无延迟的提供最新的数据的能力。 ✅【仓库管理】&#xff1a;可以理解为对仓库的库存控制、对仓库的存储优化以及协调物流。 那么实时数据仓库就是&#xff1a;…

Windows 找不到音频输入/输出设备

0.具体现象 系统音量为0&#xff0c;且无法通过任何方式调整。音量表示非静音表示&#xff0c;而是下图这个叉。 1. 解决方案 以Windows 11电脑为例&#xff0c;从搜索框输入【设备管理器】&#xff0c;进入界面如下&#xff1a; 发现一个感叹号的设备【Intel High Defini…

对象与类的一些基本定义与例子(C++)

1.例题要求&#xff0c;设计一个学生类&#xff0c;实例化对象&#xff0c;并且进行赋值 我们通过创建学生的类&#xff0c;给予了学生名字和年龄的两个属性&#xff0c;我们在main函数中创建了学生类的对象s1,通过s1调用了学生类的属性并给予赋值&#xff0c;最后调用学生类打…

Nginx统计PV、NV

目录 PV 分析 UV 分析 在 Nginx 的配置文件中设置 access_log 主要涉及到以下几个方面&#xff1a; 以下是一个基本的 access_log 配置示例&#xff1a; 使用命令行工具对日志进行基本分析&#xff1a; 后端记录 对于 nginx 的 access.log 日志&#xff0c;我们可以根据日…

进程的等待(非阻塞轮询+阻塞)和替换控制详解

引言 在Linux系统中&#xff0c;进程管理是核心功能之一。理解进程的创建、执行和终止是系统编程中的基础。本文将深入探讨Linux中的进程控制机制&#xff0c;包括进程的生命周期、父子进程的交互、以及进程状态的管理 1. 进程创建&#xff1a;fork()函数 在Linux操作系统中…

Prompt万能框架与常用评估指标

引言 在人工智能的飞速发展中&#xff0c;大型语言模型&#xff08;LLM&#xff09;已成为研究和应用的热点。LLM以其强大的语言理解和生成能力&#xff0c;在诸如自然语言处理、文本生成、问答系统等多个领域展现出巨大潜力。然而&#xff0c;要充分发挥LLM的能力&#xff0c…

NSSCTF练习记录:[SWPUCTF 2021 新生赛]caidao

题目&#xff1a; 图片上给出了代码&#xff0c;是php的一句话木马 eval($_POST[wllm]);符号 符号表示后面的语句即使执行错误&#xff0c;也不报错。 eval()函数 eval()函数的作用是把括号内的字符串全部当作php代码来执行。 ** P O S T [ ′ w l l m ′ ] ∗ ∗ p o s t …

一起学习LeetCode热题100道(30/100)

30.两两交换链表中的节点(学习) 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head …

【LVS】调度算法概念

fd相当于静态 ovf相当于动态

界面控件DevExpress WinForms,支持HTML CSS提升用户体验(二)

DevExpress WinForms现在可以利用HTML/CSS强大的功能&#xff0c;帮助受DevExpress驱动的WinForms应用程序引入现代的UI元素和用户体验&#xff01; P.S&#xff1a;DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。Dev…

室内定位导航技术:蓝牙信号强度(RSSI)与三角定位算法应用

在数字化时代&#xff0c;位置服务已成为连接物理世界与数字世界的桥梁。在室内环境中&#xff0c;由于GPS信号受建筑物遮挡而失效&#xff0c;传统的室外定位技术难以满足需求&#xff0c;无法精准指引我们在商场、机场、医院等庞大而复杂的建筑内部寻路。室内定位导航技术不仅…

在 Manim 中,kwargs 传递的关键字参数

在 Manim 中&#xff0c;kwargs 用于传递关键字参数&#xff0c;常用于构造对象时。这里的关键参数有那些。我都要&#xff0c;给我整理一下 Sider Fusion 在 Manim 中&#xff0c;kwargs 允许您传递可选的关键字参数来控制动画、对象的外观和行为。尽管具体可用的参数会因对象…

【开端】通过Java 过滤器灵活配置URL访问权限,并返回403

一、绪论 在JAVA项目系统中&#xff0c;后端给前端提供接口。但是在某些场景我们需要临时控制接口是否能被访问。或关闭某一接口的访问权限。 比如某一接口被攻击了或者某一接口存在漏洞&#xff0c;在系统不关闭的情况下&#xff0c;如何控制系统的访问权限。 二、控制接口访…

CVE-2017-15715~Apache解析漏洞【春秋云境靶场渗透】

Apache解析漏洞 漏洞原理 # Apache HTTPD 支持一个文件拥有多个后缀&#xff0c;并为不同后缀执行不同的指令。比如如下配置文件&#xff1a; AddType text/html .html AddLanguage zh-CN .cn# 其给 .html 后缀增加了 media-type &#xff0c;值为 text/html &#xff1b;给 …

【案例35】销售订单公式问题导致系统宕机

问题现象 经过顾问反馈&#xff0c;发现系统现在出现卡顿&#xff0c;NCC一直在转圈。 问题分析 远程排查&#xff0c;发现在服务器从机上defalut-7发生了内存溢出&#xff0c;宕机。 生成了宕机日志。分析结果如下&#xff1a; 销售订单相关操作&#xff0c;vo太多了导致…