sharding jdbc 分库分表

今天使用sharding jdbc 来实操一下分布分表

sharding jdbc 介绍

sharding-jdbc
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。

Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。

ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

简单梳理下概念 (来自官方文档)

逻辑表
水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0 到 t_order_9,他们的逻辑表名为 t_order。

真实表
在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9。

数据节点
数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。

绑定表
指分片规则一致的主表和子表。例如:t_order 表和 t_order_item 表,均按照 order_id 分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果 SQL 为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:


SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的 SQL 应该为 2 条:


SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中 t_order 在 FROM 的最左侧,ShardingSphere 将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么 t_order_item 表的分片计算将会使用 t_order 的条件。故绑定表之间的分区键要完全相同。

广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

单表
指所有的分片数据源中只存在唯一一张的表。适用于数据量不大且不需要做任何分片操作的场景。

分片键
用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。

分片算法
通过分片算法将数据分片,支持通过 =、>=、<=、>、<、BETWEEN 和 IN 分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供 3 种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

标准分片算法
对应 StandardShardingAlgorithm,用于处理使用单一键作为分片键的 =、IN、BETWEEN AND、>、<、>=、<= 进行分片的场景。需要配合 StandardShardingStrategy 使用。

复合分片算法
对应 ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。

Hint分片算法
对应 HintShardingAlgorithm,用于处理使用 Hint 行分片的场景。需要配合 HintShardingStrategy 使用。

分片策略
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供 4 种分片策略。

标准分片策略
对应 StandardShardingStrategy。提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。 StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。 PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。 RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND, >, <, >=, <= 分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 将按照全库路由处理。

复合分片策略
对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。 ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

Hint分片策略
对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。

不分片策略
对应 NoneShardingStrategy。不分片的策略。

SQL Hint
对于分片字段非 SQL 决定,而由其他外置条件决定的场景,可使用 SQL Hint 灵活的注入分片字段。 例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 Java API 和 SQL 注释(待实现)两种方式使用

为了避免篇幅太长 贴出官网 链接 https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/

主要使用到技术点

  • sharding-jdbc
  • mybatis
  • springboot
  • mysql
  • druid

环境搭建

  • 数据库准备
    可以在单个节点建3个库或者 建3个节点每个节点 一个库
    这里为了方便 使用单个节点3个库
    建库步骤略
    效果如图
    数据库

简单说明下逻辑
1.t_order 逻辑表 使用user_id分库 在使用 gmt_create 分表其中一个月一个表
2.t_order_goods 逻辑表 使用user_id分库 在使用 gmt_create 分表其中一个月一个表
3.同时t_order 和t_order_goods为绑定库

需要每个库执行 如下SQL 创建真实的订单表以及订单商品表

/*
 Navicat Premium Data Transfer

 Source Server         : master
 Source Server Type    : MySQL
 Source Server Version : 50731
 Source Host           : localhost:3306
 Source Schema         : db1

 Target Server Type    : MySQL
 Target Server Version : 50731
 File Encoding         : 65001

 Date: 30/09/2021 16:04:12
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order_202101
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202101`;
CREATE TABLE `t_order_202101` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202102
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202102`;
CREATE TABLE `t_order_202102` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202103
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202103`;
CREATE TABLE `t_order_202103` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202104
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202104`;
CREATE TABLE `t_order_202104` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202105
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202105`;
CREATE TABLE `t_order_202105` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202106
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202106`;
CREATE TABLE `t_order_202106` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202107
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202107`;
CREATE TABLE `t_order_202107` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202108
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202108`;
CREATE TABLE `t_order_202108` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202109
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202109`;
CREATE TABLE `t_order_202109` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202110
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202110`;
CREATE TABLE `t_order_202110` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202111
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202111`;
CREATE TABLE `t_order_202111` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_202112
-- ----------------------------
DROP TABLE IF EXISTS `t_order_202112`;
CREATE TABLE `t_order_202112` (
  `id` bigint(20) NOT NULL COMMENT 'ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

SET FOREIGN_KEY_CHECKS = 1;


/*
 Navicat Premium Data Transfer

 Source Server         : master
 Source Server Type    : MySQL
 Source Server Version : 50731
 Source Host           : localhost:3306
 Source Schema         : db1

 Target Server Type    : MySQL
 Target Server Version : 50731
 File Encoding         : 65001

 Date: 30/09/2021 16:05:16
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order_goods_202101
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202101`;
CREATE TABLE `t_order_goods_202101` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202102
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202102`;
CREATE TABLE `t_order_goods_202102` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202103
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202103`;
CREATE TABLE `t_order_goods_202103` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202104
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202104`;
CREATE TABLE `t_order_goods_202104` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202105
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202105`;
CREATE TABLE `t_order_goods_202105` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202106
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202106`;
CREATE TABLE `t_order_goods_202106` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202107
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202107`;
CREATE TABLE `t_order_goods_202107` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202108
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202108`;
CREATE TABLE `t_order_goods_202108` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202109
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202109`;
CREATE TABLE `t_order_goods_202109` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202110
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202110`;
CREATE TABLE `t_order_goods_202110` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202111
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202111`;
CREATE TABLE `t_order_goods_202111` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for t_order_goods_202112
-- ----------------------------
DROP TABLE IF EXISTS `t_order_goods_202112`;
CREATE TABLE `t_order_goods_202112` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `order_sn` varchar(64) DEFAULT NULL COMMENT '订单编号',
  `goods_name` varchar(255) DEFAULT NULL COMMENT '商品名',
  `price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
  `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_update` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

SET FOREIGN_KEY_CHECKS = 1;

  • 搭建springboot脚手架
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.style</groupId>
    <artifactId>note</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>note</name>
    <description>java-note</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
                        
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1.1-jre</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.style.note.NoteApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

  • 创建order orderGoods 基本数据操作 注意看这个插入语句
@Mapper
@Repository
public interface OrderGoodsMapper {

    /**
     * 根据ID删除
     *
     * @param id 主键ID
     * @return int
     */
    @Delete("delete from t_order_goods where id = #{id}")
    int deleteByPrimaryKey(Long id);

    /**
     * 添加对象所有字段
     *
     * @param orderGoods 插入字段对象(必须含ID)
     * @return int
     */
    @Insert("insert into t_order_goods(user_id,order_sn,goods_name,price,gmt_create,gmt_update) values(#{userId},#{orderSn},#{goodsName},#{price},#{gmtCreate},#{gmtUpdate})")
    int insert(OrderGoods orderGoods);
}

@Mapper
@Repository
public interface OrderMapper {

    /**
     * 根据ID删除
     *
     * @param id 主键ID
     * @return int
     */
    @Delete("delete from t_order where id = #{id}")
    int deleteByPrimaryKey(Long id);

    /**
     * 添加对象所有字段
     *
     * @param order 插入字段对象(必须含ID)
     * @return int
     */
    @Insert("insert into t_order(user_id,order_sn,gmt_create,gmt_update) values(#{userId},#{orderSn},#{gmtCreate},#{gmtUpdate})")
    int insert(Order order);

    /**
     * 添加对象对应字段
     *
     * @param order 插入字段对象(必须含ID)
     * @return int
     */
    int insertSelective(Order order);

    /**
     * 根据ID查询
     *
     * @param id 主键ID
     * @return order
     */
    @Select("select id,user_id AS 'userId',order_sn AS 'orderSn',gmt_create AS 'gmtCreate',gmt_update AS 'gmtUpdate' from t_order where id = #{id}")
    List<Order> selectByPrimaryKey(Long id);

    /**
     * 根据ID修改对应字段
     *
     * @param order 修改字段对象(必须含ID)
     * @return int
     */
    int updateByPrimaryKeySelective(Order order);

    /**
     * 根据ID修改所有字段(必须含ID)
     *
     * @param order 修改字段对象(必须含ID)
     * @return int
     */
    int updateByPrimaryKey(Order order);


    /**
     * 根据订单编号查询
     *
     * @param orderSn 订单编号
     * @return order
     */
    @Select("select id,user_id AS 'userId',order_sn AS 'orderSn',gmt_create AS 'gmtCreate',gmt_update AS 'gmtUpdate' from t_order where order_sn = #{orderSn} limit 1 ")
    Order selectByOrderSn(String orderSn);

    /**
     * 根据订单模糊查询
     *
     * @param orderSn 订单编号
     * @return order
     */
    @Select("select id,user_id AS 'userId',order_sn AS 'orderSn',gmt_create AS 'gmtCreate',gmt_update AS 'gmtUpdate' from t_order where order_sn like concat(#{orderSn},'%')")
    List<Order> selectByLikeOrderSn(@Param("orderSn") String orderSn);

    /**
     * 范围时间戳查询
     *
     * @param startTime startTime
     * @param endTime   endTime
     * @return List<Order>
     */
    @Select("select id,user_id AS 'userId',order_sn AS 'orderSn',gmt_create AS 'gmtCreate',gmt_update AS 'gmtUpdate' from t_order where gmt_create > #{startTime} AND gmt_create < #{endTime}")
    List<Order> selectByTimeBetween(@Param("startTime") Date startTime, @Param("endTime") Date endTime);

    /**
     * 查询用户订单
     *
     * @param userId userId
     * @return List<Order>
     */
    @Select("select id,user_id AS 'userId',order_sn AS 'orderSn',gmt_create AS 'gmtCreate',gmt_update AS 'gmtUpdate' from t_order where user_id = #{userId} limit 5")
    List<Order> selectByUserId(@Param("userId") Long userId);

    /**
     * 获取用户订单列表 包含商品信息
     *
     * @param userId userId
     * @param startTime startTime
     * @param endTime endTime
     * @return List<OrderDto>
     */
    @Select("SELECT" +
            " o.id," +
            " o.user_id AS 'userId'," +
            " o.order_sn AS 'orderSn'," +
            " goods.id AS 'orderGoodsId'," +
            " goods.goods_name AS 'goodsName'," +
            " goods.price " +
            " FROM" +
            " t_order o" +
            " LEFT JOIN t_order_goods goods ON o.order_sn = goods.order_sn " +
            " WHERE " +
            " o.user_id = #{userId} AND " +
            " o.gmt_create > #{startTime} AND o.gmt_create < #{endTime}"
    )
    List<OrderDto> getOrderListByUserId(@Param("userId") Long userId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
}    
  • 重点来了 分布分表配置 以及数据源
# 应用名称
spring:
  application:
    name: note
  shardingsphere:
    enabled: true
    datasource:
      #配置数据源 	
      names: db1,db2,db3
      db1:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db1?characterEncoding=utf-8&autoReconnect=true
        username: root
        password: root
      db2:
        #com.mysql.cj.jdbc.MysqlDataSource
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db2?characterEncoding=utf-8&autoReconnect=true
        username: root
        password: root
      db3:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db3?characterEncoding=utf-8&autoReconnect=true
        username: root
        password: root

    sharding:
      #默认数据源
      defaultDataSourceName: db1
      defaultDatabaseStrategy:
        standard:
          shardingColumn: user_id
          preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloDatabaseShardingAlgorithm
      defaultTableStrategy:
        standard:
          shardingColumn: gmt_create
          preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloTableShardingAlgorithm
      #绑定表 主表和从表 需要分片规则一致
      bindingTables:  t_order,t_order_goods
      tables:
        t_order:
          #逻辑表
          logicTable: t_order
          #真实数据节点
          actualDataNodes: db${1..3}.t_order_20210${1..9},db${1..3}.t_order_20211${0..2}
          tableStrategy:
          	#标准分表策略
            standard:
              # 分片列
              shardingColumn: gmt_create
              #精准分片算法 自行实现
              preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloTableShardingAlgorithm		  #范围分片算法 
              rangeAlgorithmClassName: com.style.note.sharding.algorithm.RangeTableShardingAlgorithm
          databaseStrategy:
            #标准分库策略
            standard:
              #分库键
              shardingColumn: user_id
              # 分片算法 
              preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloDatabaseShardingAlgorithm	
          #分布式ID生成    
          keyGenerator:
            column: id
            #生成算法 UUID SNOWFLAKE
            type: SNOWFLAKE
            #可选配置
            props:
              worker-id: 132
        t_order_goods:
          logicTable: t_order_goods
          actualDataNodes: db${1..3}.t_order_goods_20210${1..9},db${1..3}.t_order_goods_20211${0..2}
          tableStrategy:
            standard:
              shardingColumn: gmt_create
              preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloTableShardingAlgorithm
              rangeAlgorithmClassName: com.style.note.sharding.algorithm.RangeTableShardingAlgorithm
          databaseStrategy:
            standard:
              shardingColumn: user_id
              preciseAlgorithmClassName: com.style.note.sharding.algorithm.PreciseModuloDatabaseShardingAlgorithm
          keyGenerator:
            column: id
            #生成算法 UUID SNOWFLAKE
            type: SNOWFLAKE
    props:
      sql:
        show: true

server:
  port: 5001

分片算法如下


/**
  * 精确分表算法 
  */
@Log4j2
public class PreciseModuloDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
        //availableTargetNames为所有库名
        String logicTableName = preciseShardingValue.getLogicTableName();
        log.info("logicTableName: " + logicTableName);
        String columnName = preciseShardingValue.getColumnName();
        log.info("columnName: " + columnName);
        Long userId = preciseShardingValue.getValue();
        ArrayList<String> list = new ArrayList<>(availableTargetNames);
        int index = Math.toIntExact(userId % availableTargetNames.size());
        String s = list.get(index);
        if (StringUtils.isEmpty(s)) {
            log.error("分库错误 --s:  " + s);
        }
        log.info("库名: " + s);
        return s;
    }
}

/**
  * 精确分表算法 
  */
@Log4j2
public class PreciseModuloTableShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) {
        //availableTargetNames为所有库名
        String logicTableName = preciseShardingValue.getLogicTableName();
        log.info("logicTableName: " + logicTableName);
        String columnName = preciseShardingValue.getColumnName();
        log.info("columnName: " + columnName);
        Date gmtCreate = preciseShardingValue.getValue();
        String format = DateFormatUtils.format(gmtCreate, "yyyyMM");
        //每个库中所有的表
        ArrayList<String> list = new ArrayList<>(availableTargetNames);
        for (String s : list) {
            if (s.endsWith(format)) {
                log.info("表名: " + s);
                return s;
            }
        }
        throw new UnsupportedOperationException("未知的时间: " + format);
    }
    
}
/**
  * 范围分表算法 
  */
@Log4j2
public class RangeTableShardingAlgorithm implements RangeShardingAlgorithm<Date> {

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {
        Range<Date> valueRange = shardingValue.getValueRange();
        Date lowerEndpoint = valueRange.lowerEndpoint();
        Date upperEndpoint = valueRange.upperEndpoint();

        return availableTargetNames.stream().map(s -> {
            //t_order_202105
            int index = s.lastIndexOf("_");
            String substring = s.substring(index + 1);
            try {
                return DateUtils.parseDate(substring, "yyyyMM");
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return new Date();
        }).filter(data -> data.getTime() > lowerEndpoint.getTime() && data.getTime() < upperEndpoint.getTime())
                .map(data -> {
                    String formatDate = DateFormatUtils.format(data, "yyyyMM");
                    return "t_order_" + formatDate;
                }).collect(Collectors.toList());
    }
}    

准备工作就绪

测试一下

package com.style.note;

import com.style.note.sharding.dto.OrderDto;
import com.style.note.sharding.model.Order;
import com.style.note.sharding.model.OrderGoods;
import com.style.note.sharding.service.OrderGoodsService;
import com.style.note.sharding.service.OrderService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author leon
 * @date 2021-09-27 17:01:44
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {NoteApplication.class})
public class OrderTest {

    @Autowired
    private OrderService orderService;
    @Autowired
    private OrderGoodsService orderGoodsService;

    @Test
    public void test() {
        for (int i = 0; i < 200; i++) {
            Order order = new Order();
            order.setUserId((long) i);
            order.setOrderSn(generate());
            Calendar instance = Calendar.getInstance();
            ThreadLocalRandom current = ThreadLocalRandom.current();
            int month = current.nextInt(12);
            instance.set(Calendar.MONTH, month);
            order.setGmtCreate(instance.getTime());
            order.setGmtUpdate(new Date());
            orderService.createOrder(order);
        }
    }

    private static String generate() {
        ThreadLocalRandom current = ThreadLocalRandom.current();
        int random = current.nextInt(100000, 999999);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String format = sdf.format(new Date());
        return format + random;
    }

    @Test
    public void test2() {
        List<Order> orderList = orderService.selectById(1L);
        System.out.println("order: " + orderList);
    }

    @Test
    public void test3() {
        Calendar instance = Calendar.getInstance();
        instance.set(Calendar.MONTH, 6);
        List<Order> orderList = orderService.selectByTimeBetween(instance.getTime(), new Date());
        System.out.println("order: " + orderList);
    }

    @Test
    public void test4() {
        Order order = orderService.selectByOrderSn("20210928151457956587");
        System.out.println("order: " + order);
    }

    @Test
    public void test5() {
        List<Order> orderList = orderService.selectByLikeOrderSn("202109");
        System.out.println("order: " + orderList);
    }

    @Test
    public void test6() {
        List<Order> orderList = orderService.selectByUserId(1L);
        System.out.println("orderList: " + orderList);
    }

    @Test
    public void test7() {
        Order order = new Order();
        order.setUserId((long) 5);
        order.setOrderSn(generate());
        Calendar instance = Calendar.getInstance();
        ThreadLocalRandom current = ThreadLocalRandom.current();
        int month = current.nextInt(12);
        instance.set(Calendar.MONTH, month);
        order.setGmtCreate(instance.getTime());
        order.setGmtUpdate(instance.getTime());
        orderService.createOrder(order);

        OrderGoods orderGoods = new OrderGoods();
        orderGoods.setUserId((long) 5);
        orderGoods.setOrderSn(order.getOrderSn());
        orderGoods.setGoodsName("AAA");
        orderGoods.setPrice(BigDecimal.valueOf(200));
        orderGoods.setGmtCreate(instance.getTime());
        orderGoods.setGmtUpdate(instance.getTime());
        orderGoodsService.addOrderGoods(orderGoods);

    }
	
	@Test
    public void test8() {

        for (int i = 0; i < 200; i++) {
            Order order = new Order();
            order.setUserId((long) i);
            order.setOrderSn(generate());
            Calendar instance = Calendar.getInstance();
            ThreadLocalRandom current = ThreadLocalRandom.current();
            int month = current.nextInt(12);
            instance.set(Calendar.MONTH, month);
            order.setGmtCreate(instance.getTime());
            order.setGmtUpdate(instance.getTime());
            orderService.createOrder(order);

            OrderGoods orderGoods = new OrderGoods();
            orderGoods.setUserId((long) i);
            orderGoods.setOrderSn(order.getOrderSn());
            orderGoods.setGoodsName("AAA");
            orderGoods.setPrice(BigDecimal.valueOf(200));
            orderGoods.setGmtCreate(instance.getTime());
            orderGoods.setGmtUpdate(instance.getTime());
            orderGoodsService.addOrderGoods(orderGoods);
        }
        
    }

    @Test
    public void test9() {
    	//需要执行test8测试 插入测试数据
        Long userId = 9L;
        Calendar instance = Calendar.getInstance();
        instance.set(Calendar.MONTH, 11);
        Date startTime = instance.getTime();
        List<OrderDto> orderDtoList = orderService.getOrderListByUserId(userId,new Date(), startTime);
        System.out.println("orderDtoList: " + orderDtoList);
    }

}

重点看下test7的执行结果
结果1
结果2
可以看到 order 和 orerGoods 都正确插入对应的表中

再来看看test8方法的操作
结果3
可以看到准确识别出对应的数据库 以及对应的表 并打印了相关数据

至此 圆满结束 有问题可以直接留言
提前祝各位国庆节 出行顺利

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值