SpringBoot 集成 Sharding-JDBC(一):数据分片

在深入探讨 Sharding-JDBC 之前,建议读者先了解数据库分库分表的基本概念和应用场景。如果您还没有阅读过相关的内容,可以先阅读我们之前的文章:

关系型数据库海量数据存储策略-CSDN博客

这篇文章将帮助您更好地理解分库分表的基本原理和实现方法。

1. Sharding-JDBC 介绍

1.1. 背景

Sharding-JDBC 最初是由当当网内部开发的一款分库分表框架,于2017年开始对外开源。经过社区贡献者的不断迭代,功能逐渐完善,并于2020年4月16日正式成为 Apache 软件基金会的顶级项目,更名为 ShardingSphere。

Sharding-Sphere官网:Apache ShardingSphere

Sharding-Sphere官方文档:Overview :: ShardingSphere

Sharding-Sphere中文文档:概览 :: ShardingSphere

Sharding-Sphere中文文档2:概览 :: ShardingSphere

1.2. 生态圈

现在的 ShardingSphere 不再仅仅指某个框架,而是一个完整的生态圈,包括以下三个主要组件:

  • Sharding-JDBC: 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供额外服务。
  • Sharding-Proxy: 提供数据库代理服务,支持多种数据库协议。
  • Sharding-Sidecar: 以容器化方式部署,支持 Kubernetes 环境。

​ 

1.3. Sharding-Sphere 与 MyCat 的区别

MycatSharding-JDBCSharding-ProxySharding-Sidecar

官方网站

官方网站

官方网站

官方网站

官方网站

源码地址

GitHub

GitHub

GitHub

GitHub

官方文档

Mycat 权威指南

官方文档

官方文档

官方文档

开发语言

Java

Java

Java

Java

开源协议

GPL-2.0/GPL-3.0

Apache-2.0

Apache-2.0

Apache-2.0

数据库

MySQL
Oracle
SQL Server
PostgreSQL
DB2
MongoDB
SequoiaDB

MySQL
Oracle
SQLServer
PostgreSQL
任何遵循 SQL92 标准的数据库

MySQL/PostgreSQL

MySQL/PostgreSQL

连接数

应用语言

任意

Java

任意

任意

代码入侵

需要修改代码

性能

损耗略高

损耗低

损耗略高

损耗低

无中心化

静态入口

管理控制台

Mycat-web

Sharding-UI

Sharding-UI

Sharding-UI

分库分表

单库多表/多库单表

✔️

✔️

✔️

多租户方案

✔️

--

--

--

读写分离

✔️

✔️

✔️

✔️

分片策略定制化

✔️

✔️

✔️

✔️

分布式主键

✔️

✔️

✔️

✔️

标准化事务接口

✔️

✔️

✔️

✔️

XA强一致事务

✔️

✔️

✔️

✔️

柔性事务

--

✔️

✔️

✔️

配置动态化

开发中

✔️

✔️

✔️

编排治理

开发中

✔️

✔️

✔️

数据脱敏

--

✔️

✔️

✔️

可视化链路追踪

--

✔️

✔️

✔️

弹性伸缩

开发中

开发中

开发中

开发中

多节点操作

分页
去重
排序
分组
聚合

分页
去重
排序
分组
聚合

分页
去重
排序
分组
聚合

分页
去重
排序
分组
聚合

跨库关联

跨库 2 表 Join
ER Join
基于 caltlet 的多表 Join

--

--

--

IP 白名单

✔️

--

--

--

SQL 黑名单

✔️

--

--

--

1.4. Sharding-JDBC 特点

  • 轻量级框架: 在 Java 的 JDBC 层提供额外服务,使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖。
  • 兼容性强: 完全兼容 JDBC 和各种 ORM 框架,如 JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 支持多种数据库连接池: 如 DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 广泛支持数据库: 支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 

1.5. Sharding-JDBC优点

1. 透明性:

  • Sharding-JDBC 作为 JDBC 驱动的增强,对应用程序来说是透明的,无需修改业务代码即可实现数据库分片。
  • 配置简单,通过配置文件或注解方式,可以方便地进行分片规则的配置。

2. 高性能:

  • Sharding-JDBC 是基于 Java 的字节码增强技术,性能损耗较小,能够高效地处理高并发请求。
  • 支持并行执行多个分片上的查询,提高查询效率。

3. 功能丰富:支持读写分离、分布式事务、数据加密。

1.6. Sharding-JDBC缺点

1. 功能限制

  • 跨库操作存在限制,性能和效率可能受影响。
  • 不支持所有复杂的 SQL 语句,需要手动优化或改写。

2. 维护成本

  • 分片后的数据库管理和维护复杂,需要更多运维工作。
  • 故障恢复面临更大挑战,需要完善的备份和恢复机制。

3. 复杂性

  • 虽然配置相对简单,但对于复杂的分片规则和多表关联查询,配置和管理可能会变得复杂。
  • 对于初学者来说,理解和掌握 Sharding-JDBC 的全部功能和配置可能需要一定的时间。

2. 数据分片

2.1. 核心概念

  • 逻辑表:水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为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的条件。故绑定表之间的分区键要完全相同。

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

2.2. Sharding-JDBC 数据分片执行原理

2.2.1. SQL解析

1. 解析规则(Parsing Rules)

  • 作用:定义如何将 SQL 语句解析成抽象语法树(AST)。
  • 实现:
    • 词法分析:将 SQL 语句分解成一个个的词法单元(Token)。
    • 语法分析:将词法单元组合成抽象语法树(AST)。
  • 示例:对于 SQL 语句 SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18;,解析规则会生成如下的 AST:

为了便于理解,抽象语法树中的关键字的Token用绿色表示,变量的Token用红色表示,灰色表示需要进一步拆分。 

2. 提取规则(Extraction Rules)

  • 作用:从解析生成的 AST 中提取关键信息,如表名、列名、条件等。
  • 实现:
    • 遍历 AST:通过递归或迭代的方式遍历 AST,提取所需的信息。
    • 信息提取:提取表名、列名、条件表达式等。
  • 示例:对于 SQL 语句 SELECT * FROM table WHERE id = 1;,提取规则会提取以下信息:
    • 表名:table
    • 条件:id = 1

3. 填充规则(Filling Rules)

  • 作用:根据提取的关键信息和分片规则,填充路由信息。
  • 实现:
    • 分片键提取:从提取的信息中找到分片键。
    • 路由计算:根据分片规则计算出目标数据源和表。
  • 示例:假设分片规则是按 user_id 的奇偶性分片,对于 SQL 语句 SELECT * FROM order WHERE user_id = 1001;,填充规则会计算出目标表为 order_1。

4. 优化规则(Optimization Rules)

  • 作用:对解析和填充后的 SQL 语句进行优化,提高执行效率。
  • 实现:
    • 子查询优化:将复杂的子查询转换为更高效的查询。
    • 索引优化:根据索引信息优化查询计划
    • 并行执行:将可以并行执行的查询拆分成多个子查询,提高执行速度。
  • 示例:对于 SQL 语句 SELECT * FROM order WHERE user_id IN (1001, 1002, 1003);,优化规则可能会将其拆分成多个子查询:
    SELECT * FROM order_1 WHERE user_id = 1001;SELECT * FROM order_0 WHERE user_id = 1002;SELECT * FROM order_1 WHERE user_id = 1003;

 2.2.2. SQL路由

根据解析上下文匹配数据库和表的分片策略,并生成路由路径。 对于携带分片键的SQL,根据分片键的不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是IN)和范围路由(分片键的操作符是BETWEEN)。 不携带分片键的SQL则采用广播路由。路由引擎的整体结构划分如下图。

2.2.2.1. 分片路由

分片路由根据分片键进行路由,将 SQL 请求路由到特定的数据库和表中。根据分片键的不同操作符,分片路由可以进一步划分为以下几种类型:

1. 直接路由:

  • 条件:通过 Hint API 直接指定路由至库表,且只分库不分表。
  • 特点:避免 SQL 解析和结果归并,兼容性强,支持复杂 SQL。

2. 标准路由:

  • 条件:不包含关联查询或仅包含绑定表之间的关联查询。
  • 特点:分片键操作符为等号时路由到单库(表),操作符为 BETWEEN 或 IN 时可能路由到多库(表)。
  • 示例:
SELECT * FROM t_order WHERE order_id IN (1, 2);

路由结果:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

3. 笛卡尔路由:

条件:非绑定表之间的关联查询。

特点:无法根据绑定表关系定位分片规则,需要拆解为笛卡尔积组合执行,性能较低。

示例:

SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE order_id IN (1, 2);

路由结果:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);    
2.2.2.2. 广播路由

广播路由将 SQL 请求路由到所有配置的数据源或表中,适用于不携带分片键的 SQL。根据 SQL 类型,广播路由可以进一步划分为以下几种类型:

1. 全库表路由:

  • 条件:处理对数据库中与其逻辑表相关的所有真实表的操作,如不带分片键的 DQL 和 DML,以及 DDL。
  • 示例:
SELECT * FROM t_order WHERE good_prority IN (1, 10);

路由结果:

SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);

 2. 全库路由:

  • 条件:处理对数据库的操作,如 SET 类型的数据库管理命令和 TCL 事务控制语句。
  • 示例:
SET autocommit=0;

路由结果:

SET autocommit=0; -- 在 t_order_0 上执行
SET autocommit=0; -- 在 t_order_1 上执行

3. 全实例路由:

  • 条件:处理 DCL 操作,如授权语句。
  • 示例:
CREATE USER customer@127.0.0.1 identified BY '123';

路由结果:

CREATE USER customer@127.0.0.1 identified BY '123'; -- 在所有实例上执行

4. 单播路由: 

  • 条件:获取某一真实表信息的场景,仅需要从任意库中的任意真实表中获取数据。
  • 示例:
DESCRIBE t_order;

路由结果:

DESCRIBE t_order_0; -- 任意选择一个真实表执行

 5. 阻断路由:

  • 条件:屏蔽 SQL 对数据库的操作,如 USE 命令。
  • 示例:
USE order_db;

 路由结果:

-- 不执行该命令

2.2.3.  SQL改写

SQL 改写是将面向逻辑库与逻辑表编写的 SQL 转换为在真实数据库中可执行的 SQL。它主要包括正确性改写和优化改写两部分。

2.2.3.1. 正确性改写

将面向逻辑库与逻辑表编写的 SQL 转换为在真实数据库中可以正确执行的 SQL。

场景

逻辑 SQL

改写后的 SQL

简单场景

SELECT order_id

FROM t_order

WHERE order_id=1;

SELECT order_id

FROM t_order_1

WHERE order_id=1;

复杂场景

SELECT order_id

FROM t_order

WHERE order_id=1

AND remarks=' t_order xxx';

SELECT order_id

FROM t_order_1

WHERE order_id=1

AND remarks=' t_order xxx';

表名作为字段标识符

SELECT t_order.order_id

FROM t_order

WHERE t_order.order_id=1

AND remarks=' t_order xxx';

SELECT t_order_1.order_id

FROM t_order_1

WHERE t_order_1.order_id=1

AND remarks=' t_order xxx';

表别名

SELECT t_order.order_id

FROM t_order AS t_order

WHERE t_order.order_id=1

AND remarks=' t_order xxx';

SELECT t_order.order_id

FROM t_order_1 AS t_order

WHERE t_order.order_id=1

AND remarks=' t_order xxx';

2.2.3.1.1. 标识符改写

标识符

描述

表名称

将逻辑表名称改写为真实表名称。

索引

名称

在某些数据库中,索引以表为维度创建,可以重名;在另一些数据库中,索引以数据库为维度创建,要求名称唯一。

Schema

名称

将逻辑 Schema 替换为真实 Schema。

2.2.3.1.2. 补列

场景

逻辑 SQL

改写后的 SQL

GROUP BY 和

ORDER BY

SELECT order_id

FROM t_order

ORDER BY user_id;

SELECT order_id, user_id AS ORDER_BY_DERIVED_0

FROM t_order

ORDER BY user_id;

复杂场景

SELECT o.*

FROM t_order o, t_order_item i

WHERE o.order_id=i.order_id

ORDER BY user_id, order_item_id;

SELECT o.*, order_item_id AS ORDER_BY_DERIVED_0

FROM t_order o, t_order_item i

WHERE o.order_id=i.order_id

ORDER BY user_id, order_item_id;

AVG 聚合函数

SELECT AVG(price)

FROM t_order

WHERE user_id=1;

SELECT COUNT(price) AS AVG_DERIVED_COUNT_0, SUM(price) AS AVG_DERIVED_SUM_0

FROM t_order

WHERE user_id=1;

自增主键

INSERT INTO t_order (field1,field2)

VALUES (10, 1);

INSERT INTO t_order (field1,field2, order_id)

VALUES (10, 1, xxxxx);

2.2.3.1.3. 分页修正

场景

逻辑 SQL

改写后的 SQL

分页查询

SELECT score

FROM t_score

ORDER BY score DESC

LIMIT 1, 2;

SELECT score FROM t_score_0 ORDER BY score DESC LIMIT 0, 3;

SELECT score FROM t_score_1 ORDER BY score DESC LIMIT 0, 3;

 2.2.3.1.4. 批量拆分

场景

逻辑 SQL

改写后的 SQL(order_id 按奇偶分片)

批量插入

INSERT INTO t_order (order_id, xxx)

VALUES (1, 'xxx'), (2, 'xxx'), (3, 'xxx');

INSERT INTO t_order_0 (order_id, xxx) VALUES (2, 'xxx');

INSERT INTO t_order_1 (order_id, xxx) VALUES (1, 'xxx'), (3, 'xxx');

IN

查询

SELECT *

FROM t_order

WHERE order_id IN (1, 2, 3);

SELECT * FROM t_order_0 WHERE order_id IN (2);

SELECT * FROM t_order_1 WHERE order_id IN (1, 3);

2.2.3.2. 优化改写

类型

描述

单节点优化

如果 SQL 路由到单个节点,无需进行额外的改写,以减少不必要的开销。

流式归并优化

为包含 GROUP BY 的 SQL 增加 ORDER BY 以实现流式归并,提高性能。

2.2.4. SQL执行

 2.2.4.1. 连接模式

ShardingSphere 提出了两种连接模式:内存限制模式(MEMORY_STRICTLY) 和 连接限制模式(CONNECTION_STRICTLY)。

1. 内存限制模式(MEMORY_STRICTLY)

  • 特点:不对数据库连接数量做限制。
  • 适用场景:适合 OLAP 操作,通过多线程并发处理多个表的查询,提升执行效率。
  • 优点:最大化执行效率,优先选择流式归并,节省内存。
  • 缺点:可能占用大量数据库连接资源。

2. 连接限制模式(CONNECTION_STRICTLY)

  • 特点:严格控制数据库连接数量。
  • 适用场景:适合 OLTP 操作,通常带有分片键,路由到单一的分片。
  • 优点:防止过多占用数据库连接资源,保证在线系统数据库资源的充分利用。
  • 缺点:可能牺牲一定的执行效率,采用内存归并。
2.2.4.2. 自动化执行引擎

ShardingSphere 的自动化执行引擎在内部消化了连接模式的概念,用户无需手动选择模式,执行引擎会根据当前场景自动选择最优的执行方案。

1.  准备阶段

  • 结果集分组:根据 maxConnectionSizePerQuery 配置项,将 SQL 的路由结果按数据源名称分组。
  • 连接模式计算:
    • 计算每个数据库实例在 maxConnectionSizePerQuery 允许范围内,每个连接需要执行的 SQL 路由结果组。
    • 如果一个连接需要执行的请求数量大于1,采用内存归并;否则,采用流式归并。

  • 执行单元创建:创建执行单元时,以原子性方式一次性获取所需的所有数据库连接,避免死锁

2. 执行阶段

  • 分组执行:将准备阶段生成的执行单元分组下发至底层并发执行引擎,并发送执行事件。
  • 归并结果集生成:根据连接模式生成内存归并结果集或流式归并结果集,并传递至结果归并引擎。
2.2.4.3. 关键优化
  • 避免锁定:对于只需要获取1个数据库连接的操作,不进行锁定,提升并发效率。
  • 资源锁定:仅在内存限制模式下进行资源锁定,连接限制模式下不会产生死锁。

2.2.5. 结果归并

ShardingSphere 支持的结果归并类型从功能上分为五种:遍历、排序、分组、分页和聚合。从结构上划分,可分为流式归并、内存归并和装饰者归并。

 2.2.5.1. 结构划分

1. 流式归并

  • 特点:逐条获取数据,减少内存消耗。
  • 适用场景:适用于大多数查询,尤其是大数据量的查询。
  • 类型:遍历归并、排序归并、流式分组归并。

2. 内存归并

  • 特点:将所有数据加载到内存中进行处理。
  • 适用场景:适用于分组项与排序项不一致的查询。
  • 类型:内存分组归并。

3. 装饰者归并

  • 特点:在流式归并和内存归并的基础上进行功能增强。
  • 类型:分页归并、聚合归并。
2.2.5.2. 功能划分

1. 遍历归并

  • 特点:最简单的归并方式,将多个数据结果集合并为一个单向链表。
  • 实现:遍历完当前数据结果集后,链表元素后移一位,继续遍历下一个数据结果集。

2. 排序归并

  • 特点:适用于包含 ORDER BY 语句的查询。
  • 实现:使用优先级队列对多个有序结果集进行归并排序。
  • 示例:
    • 假设有3个数据结果集,每个结果集已经根据分数排序。
    • 使用优先级队列将每个结果集的当前数据值进行排序。
    • 每次 next 调用时,弹出队列首位的数据值,并将游标下移一位,重新加入队列。

3.  分组归并

  • 特点:分为流式分组归并和内存分组归并。
  • 流式分组归并:
    • 适用场景:分组项与排序项一致。
    • 实现:一次将多个数据结果集中分组项相同的数据全数取出,并进行聚合计算。
  • 内存分组归并:
    • 适用场景:分组项与排序项不一致。
    • 实现:将所有数据加载到内存中进行分组和聚合。

 4. 聚合归并

  • 特点:处理聚合函数,如 MAX、MIN、SUM、COUNT 和 AVG。
  • 实现:
    • 比较类型:MAX 和 MIN,直接返回最大或最小值。
    • 累加类型:SUM 和 COUNT,将同组的数据进行累加。
    • 求平均值:AVG,通过 SUM 和 COUNT 计算。

 5. 分页归并

  • 特点:在其他归并类型基础上追加分页功能。
  • 实现:通过 LIMIT 语句进行分页,但不会将大量无意义的数据加载到内存中。
  • 示例:
    • 使用 ID 进行分页,例如:SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id;
    • 记录上次查询结果的最后一条记录的 ID 进行下一页查询,例如:SELECT * FROM t_order WHERE id > 10000000 LIMIT 10;

2.3. 使用步骤

2.3.1. 引入依赖

在 pom.xml 文件中添加 Sharding-JDBC 的依赖:

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

​2.3.2. 添加配置文件

注意:

  1. 多个数据源的数据库连接池可以不同。
  2. 多个数据源的数据库驱动类型必须相同。
2.3.2.1. 精确分片

对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

1. 编写yaml文件

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:standard: # 用于单分片键的标准分片场景# 添加数据分库字段(根据字段插入数据到那个表)sharding-column: id# 精确分片precise-algorithm-class-name: ${common.algorithm.db}# 分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略standard: # 用于单分片键的标准分片场景# 分片列名称sharding-column: id# 精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器precise-algorithm-class-name: ${common.algorithm.tb}props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false# 配置分片策略
common:algorithm:db: com.zjp.shadingjdbcdemo.strategy.database.DatabasePreciseAlgorithmtb: com.zjp.shadingjdbcdemo.strategy.table.TablePreciseAlgorithm

2. 添加分库规则

实现 PreciseShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.database;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.util.Collection;public class DatabasePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {/*** 精确分片** @param collection           数据源集合* @param preciseShardingValue 分片参数* @return 数据库*/@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {// 自定义分片方法// 1.获取分片键的值Long value = preciseShardingValue.getValue();// 2.获取逻辑表名String logicTableName = preciseShardingValue.getLogicTableName();// 3.获取数据库名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 4.根据分片键的值,计算出对应的数据源名称return dbNames.get((int) (value % 2));}
}

3. 添加分表规则

实现 PreciseShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.table;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.util.Collection;public class TablePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {/*** 精确分片** @param collection           数据源集合* @param preciseShardingValue 分片参数* @return 数据库*/@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {// 自定义分片方法// 1.获取分片键值Long value = preciseShardingValue.getValue();// 2.获取逻辑表名String logicTableName = preciseShardingValue.getLogicTableName();// 3.获取真实表总数int size = collection.size();// 4.计算表名int v;long n;do {v = (int) (Math.pow(2, Math.ceil(Math.log(size) / Math.log(2))) - 1);n = value & v;} while (n >= size);// 5.获取真实表名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 6.返回真实表return dbNames.get((int) n);}
}
2.3.2.2. 范围分片

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

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

1. 编写yaml文件

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:standard: # 用于单分片键的标准分片场景# 添加数据分库字段(根据字段插入数据到那个表)sharding-column: id# 精确分片precise-algorithm-class-name: ${common.algorithm.db}# 范围分片range-algorithm-class-name: ${common.algorithm.db}#分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略standard: # 用于单分片键的标准分片场景# 分片列名称sharding-column: id# 精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器precise-algorithm-class-name: ${common.algorithm.tb}# 范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器range-algorithm-class-name: ${common.algorithm.tb}props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false# 配置分片策略
common:algorithm:db: com.zjp.shadingjdbcdemo.strategy.database.DatabaseRangeAlgorithmtb: com.zjp.shadingjdbcdemo.strategy.table.TableRangeAlgorithm

2. 添加分库范围查询和精确查询规则

  1. 范围查询:实现 RangeShardingAlgorithm 接口,重写 doSharding 方法。
  2. 精确查询:实现 PreciseShardingAlgorithm 接口,重写 doSharding 方法实现。

可以用两个类分别实现,也可以一个类同时实现。

package com.zjp.shadingjdbcdemo.strategy.database;import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import java.util.Collection;public class DatabaseRangeAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {/*** 精确分片** @param collection           数据源集合* @param preciseShardingValue 分片参数* @return 数据库*/@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {// 自定义分片方法// 1.获取分片键的值Long value = preciseShardingValue.getValue();// 2.获取逻辑表名String logicTableName = preciseShardingValue.getLogicTableName();// 3.获取数据库名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 4.根据分片键的值,计算出对应的数据源名称return dbNames.get((int) (value % 2));}/*** 范围分片** @param collection         数据源集合* @param rangeShardingValue 分片参数* @return 直接返回源*/@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {// 1.获取分片键名String shardingColumn = rangeShardingValue.getColumnName();// 2.获取逻辑表名String logicTableName = rangeShardingValue.getLogicTableName();// 3.获取分片键值范围Range<Long> valueRange = rangeShardingValue.getValueRange();// 4.根据分片键值范围,进行范围匹配long lower = 0L;long upper = Long.MAX_VALUE;// 存在下限if (valueRange.hasLowerBound()) {lower = valueRange.lowerEndpoint();}// 存在上限if (valueRange.hasUpperBound()) {upper = valueRange.upperEndpoint();}// 5.过滤并返回return collection;}
}

3. 添加分表规则

  1. 范围查询:实现 RangeShardingAlgorithm 接口,重写 doSharding 方法。
  2. 精确查询:实现 PreciseShardingAlgorithm 接口,重写 doSharding 方法。

可以用两个类分别实现,也可以一个类同时实现。

package com.zjp.shadingjdbcdemo.strategy.table;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import java.util.Arrays;
import java.util.Collection;public class TableRangeAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {/*** 精确分片** @param collection           数据源集合* @param preciseShardingValue 分片参数* @return 数据库*/@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {// 自定义分片方法// 1.获取分片键值Long value = preciseShardingValue.getValue();// 2.获取逻辑表名String logicTableName = preciseShardingValue.getLogicTableName();// 3.获取真实表总数int size = collection.size();// 4.计算表名long n;int v = (int) (Math.pow(13, Math.ceil(Math.log(size * 7) / Math.log(2))) - 1);do {n = value & v;v = v / 2;} while (n >= size);// 5.获取真实表名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 6.返回真实表return dbNames.get((int) n);}/*** 范围分片** @param collection         数据源集合* @param rangeShardingValue 分片参数* @return 直接返回源*/@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {//逻辑表名称// 1.获取分片键名String shardingColumn = rangeShardingValue.getColumnName();// 2.获取逻辑表名String logicTableName = rangeShardingValue.getLogicTableName();// 3.获取分片键值范围Range<Long> valueRange = rangeShardingValue.getValueRange();// 4.根据分片键值范围,进行范围匹配long lower = 0L;long upper = Long.MAX_VALUE;// 存在下限if (valueRange.hasLowerBound()) {lower = valueRange.lowerEndpoint();}// 存在上限if (valueRange.hasUpperBound()) {upper = valueRange.upperEndpoint();}return collection;}
}
2.3.2.3. 行表达式分片

对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 2} 表示t_user表根据u_id模2,而分成2张表,表名称为t_user_0到t_user_1。

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:inline: # 行表达式分片策略# 添加数据分库字段(根据字段插入数据到那个表)sharding-column: id# 分片算法表达式 => 通过id取余algorithm-expression: ds$->{id % 2}# 分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略inline: # 行表达式分片策略# 分片列名称sharding-column: id# 分片算法表达式 => 通过id取余algorithm-expression: t_user_$->{id % 2 + 1}props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false
2.3.2.4. 复合分片

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

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

注意:

分片键只支持Integer类型,虽然能传其他类型,但是值是无法处理的。

1. 编写yaml文件

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:complex: #用于多分片键的复合分片场景# 添加数据分库字段(根据字段插入数据到那个表)sharding-columns: id,agealgorithm-class-name: ${common.algorithm.db}#分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略complex: #用于多分片键的复合分片场景sharding-columns: id,agealgorithm-class-name: ${common.algorithm.tb}props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false# 配置分片策略
common:algorithm:db: com.zjp.shadingjdbcdemo.strategy.database.DatabaseComplexAlgorithmtb: com.zjp.shadingjdbcdemo.strategy.table.TableComplexAlgorithm

2. 添加分库范围查询和精确查询规则

实现 ComplexKeysShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.database;import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class DatabaseComplexAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {/*** 复合分片** @param collection               数据源集合* @param complexKeysShardingValue 分片键的值集合* @return 需要查找的数据源集合*/@Overridepublic Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {// 自定义复合分片规则// 1.获取分片键的值// 等于号 in 的值Map<String, Collection<Integer>> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();// 实现  >,>=, <=,<  和 BETWEEN AND 等操作Map<String, Range<Integer>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();// 2. 获取逻辑表名String logicTableName = complexKeysShardingValue.getLogicTableName();// 获取age的值Collection<Integer> ageValues = columnNameAndShardingValuesMap.get("age");Range<Integer> ageRange = columnNameAndRangeValuesMap.get("age");// 获取id的值Collection<Integer> idValues = columnNameAndShardingValuesMap.get("id");Range<Integer> idRange = columnNameAndRangeValuesMap.get("id");// 3.获取数据库名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 4.根据分片键的值,计算出对应的数据源名称if (CollectionUtils.isNotEmpty(ageValues)) {// 如果 ageValues 不为空,根据 age 值对 dbNames 进行取模操作return ageValues.stream().map(age -> dbNames.get(age % 2)).collect(Collectors.toSet());} else if (ageRange != null) {// 如果 ageRange 不为空,根据 age 范围判断属于哪个分区int numPartitions = dbNames.size();int maxAge = 150;int partitionSize = (maxAge + 1) / numPartitions;// 遍历每个分区Set<String> resultDbs = new HashSet<>();for (int i = 0; i < numPartitions; i++) {int start = i * partitionSize;int end = (i + 1) * partitionSize - 1;if (i == numPartitions - 1) {end = maxAge;}try {// 检查 ageRange 是否与当前分区范围有交集,如果两个区间没有交集,该方法将抛出IllegalArgumentException。ageRange.intersection(Range.closed(start, end));resultDbs.add(dbNames.get(i));} catch (IllegalArgumentException e) {}}return resultDbs;}// 默认情况下返回 collectionreturn collection;}
}

3. 添加分表规则

实现 ComplexKeysShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.table;import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class TableRangeAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {/*** 精确分片** @param collection           数据源集合* @param preciseShardingValue 分片参数* @return 数据库*/@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {// 自定义分片方法// 1.获取分片键值Long value = preciseShardingValue.getValue();// 2.获取逻辑表名String logicTableName = preciseShardingValue.getLogicTableName();// 3.获取真实表总数int size = collection.size();// 4.计算表名long n;int v = (int) (Math.pow(13, Math.ceil(Math.log(size * 7) / Math.log(2))) - 1);do {n = value & v;v = v / 2;} while (n >= size);// 5.获取真实表名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 6.返回真实表return dbNames.get((int) n);}/*** 范围分片** @param collection         数据源集合* @param rangeShardingValue 分片参数* @return 直接返回源*/@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {//逻辑表名称// 1.获取分片键名String shardingColumn = rangeShardingValue.getColumnName();// 2.获取逻辑表名String logicTableName = rangeShardingValue.getLogicTableName();// 3.获取分片键值范围Range<Long> valueRange = rangeShardingValue.getValueRange();// 4.根据分片键值范围,进行范围匹配long lower = 0L;long upper = Long.MAX_VALUE;// 存在下限if (valueRange.hasLowerBound()) {lower = valueRange.lowerEndpoint();}// 存在上限if (valueRange.hasUpperBound()) {upper = valueRange.upperEndpoint();}return collection;}
}

拓展:

Range 方法:

方法名

参数

返回值

说明

是否静态

closed(T lower, T upper)

T lower, T upper

Range<T>

创建一个闭区间 [lower, upper],包括下界和上界。

open(T lower, T upper)

T lower, T upper

Range<T>

创建一个开区间 (lower, upper),不包括下界和上界。

closedOpen(T lower, T upper)

T lower, T upper

Range<T>

创建一个半闭区间 [lower, upper),包括下界但不包括上界。

openClosed(T lower, T upper)

T lower, T upper

Range<T>

创建一个半闭区间 (lower, upper],不包括下界但包括上界。

greaterThan(T lower)

T lower

Range<T>

创建一个大于给定值的区间 (lower, +∞)。

lessThan(T upper)

T upper

Range<T>

创建一个小于给定值的区间 (-∞, upper)。

atLeast(T lower)

T lower

Range<T>

创建一个大于或等于给定值的区间 [lower, +∞)。

atMost(T upper)

T upper

Range<T>

创建一个小于或等于给定值的区间 (-∞, upper]。

all()

Range<T>

创建一个包含所有可能值的区间 (-∞, +∞)。

contains(T value)

T value

boolean

检查区间是否包含给定值。

encloses(Range<T> other)

Range<T> other

boolean

检查当前区间是否完全包含另一个区间。

isConnected(Range<T> other)

Range<T> other

boolean

检查两个区间是否有交集或相邻。

intersection(Range<T> other)

Range<T> other

Range<T>

返回两个区间的交集。如果两个区间没有交集,该方法将抛出IllegalArgumentException。

span(Range<T> other)

Range<T> other

Range<T>

返回两个区间的并集。

hasLowerBound()

boolean

检查区间是否有下界。

hasUpperBound()

boolean

检查区间是否有上界。

lowerEndpoint()

T

返回区间的下界。

upperEndpoint()

T

返回区间的上界。

lowerBoundType()

BoundType

返回区间的下界类型(闭合或开放)。

upperBoundType()

BoundType

返回区间的上界类型(闭合或开放)。

示例代码:
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@Slf4j
@SpringBootTest
public class ShardingJdbcDemoApplicationTests {public static void main(String[] args) {// 创建一个闭区间 [1, 5]Range<Integer> closedRange = Range.closed(1, 5);System.out.println("Closed range: " + closedRange);// 创建一个开区间 (1, 5)Range<Integer> openRange = Range.open(1, 5);System.out.println("Open range: " + openRange);// 创建一个半闭区间 [1, 5)Range<Integer> closedOpenRange = Range.closedOpen(1, 5);System.out.println("Closed-open range: " + closedOpenRange);// 创建一个半闭区间 (1, 5]Range<Integer> openClosedRange = Range.openClosed(1, 5);System.out.println("Open-closed range: " + openClosedRange);// 创建一个大于给定值的区间 (3, +∞)Range<Integer> greaterThanRange = Range.greaterThan(3);System.out.println("Greater than range: " + greaterThanRange);// 创建一个小于给定值的区间 (-∞, 3)Range<Integer> lessThanRange = Range.lessThan(3);System.out.println("Less than range: " + lessThanRange);// 创建一个大于或等于给定值的区间 [3, +∞)Range<Integer> atLeastRange = Range.atLeast(3);System.out.println("At least range: " + atLeastRange);// 创建一个小于或等于给定值的区间 (-∞, 3]Range<Integer> atMostRange = Range.atMost(3);System.out.println("At most range: " + atMostRange);// 创建一个包含所有可能值的区间 (-∞, +∞)Range<Integer> allRange = Range.all();System.out.println("All range: " + allRange);// 检查区间是否包含给定值boolean contains = closedRange.contains(3);System.out.println("Contains 3: " + contains);// 检查一个区间是否完全包含另一个区间boolean encloses = closedRange.encloses(openRange);System.out.println("Encloses open range: " + encloses);// 检查两个区间是否有交集或相邻boolean isConnected = closedRange.isConnected(openRange);System.out.println("Is connected: " + isConnected);// 返回两个区间的交集Range<Integer> intersection = closedRange.intersection(openRange);System.out.println("Intersection: " + intersection);// 返回两个区间的并集Range<Integer> span = closedRange.span(openRange);System.out.println("Span: " + span);// 检查区间是否有下界和上界boolean hasLowerBound = closedRange.hasLowerBound();boolean hasUpperBound = closedRange.hasUpperBound();System.out.println("Has lower bound: " + hasLowerBound);System.out.println("Has upper bound: " + hasUpperBound);// 返回区间的下界和上界int lowerEndpoint = closedRange.lowerEndpoint();int upperEndpoint = closedRange.upperEndpoint();System.out.println("Lower endpoint: " + lowerEndpoint);System.out.println("Upper endpoint: " + upperEndpoint);// 返回区间的下界类型和上界类型BoundType lowerBoundType = closedRange.lowerBoundType();BoundType upperBoundType = closedRange.upperBoundType();System.out.println("Lower bound type: " + lowerBoundType);System.out.println("Upper bound type: " + upperBoundType);}
}
2.3.2.5. Hint分片

用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

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

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

1. 编写yaml文件

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:hint: #Hint分片策略algorithm-class-name: ${common.algorithm.db}# 分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略hint: #Hint分片策略algorithm-class-name: ${common.algorithm.tb}props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false# 配置分片策略
common:algorithm:db: com.zjp.shadingjdbcdemo.strategy.database.DatabaseHintAlgorithmtb: com.zjp.shadingjdbcdemo.strategy.table.TableHintAlgorithm

2. 添加分库范围查询和精确查询规则

实现 HintShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.database;import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;import java.util.Collection;
import java.util.HashSet;public class DatabaseHintAlgorithm implements HintShardingAlgorithm<Integer> {/*** 执行自定义Hint分片逻辑。** @param collection        数据库名称集合* @param hintShardingValue 分片值对象,包含分片键和逻辑表名* @return 分片后的数据源名称集合*/@Overridepublic Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {// 自定义Hint分片方法// 1.获取分片键的值Collection<Integer> values = hintShardingValue.getValues();// 2.获取逻辑表名String logicTableName = hintShardingValue.getLogicTableName();// 3.获取数据库名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 4.根据分片键的值,计算出对应的数据源名称return values.stream().map(value -> dbNames.get((int) (value % 2))).collect(Collectors.toSet());}
}

3. 添加分表规则

实现 HintShardingAlgorithm 接口,重写 doSharding 方法。

package com.zjp.shadingjdbcdemo.strategy.table;import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class TableHintAlgorithm implements HintShardingAlgorithm<Integer> {/*** 强制路由** @param collection        数据源集合* @param hintShardingValue 分片参数* @return 数据库集合*/@Overridepublic Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {// 自定义Hint分片方法// 1.获取分片键的值Collection<Integer> values = hintShardingValue.getValues();// 2.获取逻辑表名String logicTableName = hintShardingValue.getLogicTableName();// 3.获取数据库名称,并排序List<String> dbNames = collection.stream().sorted(String::compareTo).collect(Collectors.toList());// 4.获取真实表总数int size = collection.size();// 5.根据分片键的值,计算出对应的数据源名称return values.stream().map(value -> {long n;int v = (int) (Math.pow(13, Math.ceil(Math.log(size * 7) / Math.log(2))) - 1);do {n = value & v;v = v / 2;} while (n >= size);return dbNames.get((int) n);}).collect(Collectors.toSet());}
}
2.3.2.6. 不分片

对应NoneShardingStrategy。不分片的策略。

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource: # 数据源配置,可配置多个data_source_namenames: ds0,ds1ds0: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码ds1: # <!!数据库连接池实现类> `!!`表示实例化该类type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池driver-class-name: com.mysql.cj.jdbc.Driver # 数据库驱动类名url: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false # 数据库url连接,如果是HikariCP连接池,需要换成jdbcurlusername: root # 数据库用户名password: root # 数据库密码sharding:# 唯一库数据default-data-source-name: ds0# 分库default-database-strategy:none: # 不分片# 分表tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2} #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况key-generator:column: id # 自增列名称,缺省表示不使用自增主键生成器type: SNOWFLAKE  #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID# props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性table-strategy: # 分表策略,同分库策略none: # 不分片props: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false

 2.3.3. 解决jdk8新时间类与Sharding-Sphere兼容问题

以 LocalDate 和 LocalDateTime 为例:

1. 引入依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version>
</dependency>

2. 编写 BaseTypeHandler 的实现类:

  • LocalDate适配:
package com.zjp.shadingjdbcdemo.handler;import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;@Component
@MappedTypes(LocalDate.class)
@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType)throws SQLException {ps.setObject(i, parameter);}@Overridepublic LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {return Convert.convert(new TypeReference<LocalDate>() {@Overridepublic String getTypeName() {return super.getTypeName();}},rs.getObject(columnName));}@Overridepublic LocalDate getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return Convert.convert(new TypeReference<LocalDate>() {@Overridepublic String getTypeName() {return super.getTypeName();}},rs.getObject(columnIndex));}@Overridepublic LocalDate getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return Convert.convert(new TypeReference<LocalDate>() {@Overridepublic String getTypeName() {return super.getTypeName();}},cs.getObject(columnIndex));}
}
  • LocalDateTime适配:
package com.zjp.shadingjdbcdemo.handler;import cn.hutool.core.convert.Convert;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;@Component
@MappedTypes(LocalDateTime.class)
@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)throws SQLException {ps.setObject(i, parameter);}@Overridepublic LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {return Convert.toLocalDateTime(rs.getObject(columnName));}@Overridepublic LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return Convert.toLocalDateTime(rs.getObject(columnIndex));}@Overridepublic LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return Convert.toLocalDateTime(cs.getObject(columnIndex));}
}

其他时间类可仿照上述方案仿写。

2.3.4. 关于雪花算法配置

雪花算法生成的id总共64位8个字节,结构如下:

符号位时间位工作机器标识位序列位
1位(固定位0)41位10位12位

源码如下图:

通过源码可以看出,雪花算法可以额外配置三个参数:

  1. worker.id:工作机器标识位,表示一个唯一的工作进程id,取值范围(整数):0(包含)~1024(不包含),默认值为0。
  2. max.vibration.offset:序列位,同一毫秒内生成不同的ID,取值范围(整数):0(包含)~4095(包含),默认值为1。
  3. max.tolerate.time.difference.milliseconds:最大容忍的时钟回拨毫秒数,默认值为10。如下图源码所示,最后一次生成主键的时间 lastMilliseconds 与 当前时间currentMilliseconds 做比较,如果 lastMilliseconds > currentMilliseconds则意味着时钟回调了。那么接着判断两个时间的差值(timeDifferenceMilliseconds)是否在设置的最大容忍时间阈值 max.tolerate.time.difference.milliseconds内,在阈值内则线程休眠差值时间 Thread.sleep(timeDifferenceMilliseconds),否则大于差值直接报异常。

配置示例: 

spring:shardingsphere:sharding:tables:key-generator:column: id # 主键IDtype: SNOWFLAKEprops:work:id: 0max:vibration:offset: 1tolerate:time:difference:milliseconds: 10

2.3.5. 测试

1. 创建数据表

CREATE DATABASE IF NOT EXISTS database1;USE database1;DROP TABLE IF EXISTS `t_user_1`;
DROP TABLE IF EXISTS `t_user_2`;CREATE TABLE `t_user_1` (`id` bigint(20) NOT NULL COMMENT '用户编号',`name` varchar(255) DEFAULT NULL COMMENT '用户名',`age` int(11) DEFAULT NULL COMMENT '用户年龄',`salary` double DEFAULT NULL COMMENT '用户薪资',`birthday` datetime DEFAULT NULL COMMENT '用户生日',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_user_2` (`id` bigint(20) NOT NULL COMMENT '用户编号',`name` varchar(255) DEFAULT NULL COMMENT '用户名',`age` int(11) DEFAULT NULL COMMENT '用户年龄',`salary` double DEFAULT NULL COMMENT '用户薪资',`birthday` datetime DEFAULT NULL COMMENT '用户生日',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE DATABASE IF NOT EXISTS database2;USE database2;DROP TABLE IF EXISTS `t_user_1`;
DROP TABLE IF EXISTS `t_user_2`;CREATE TABLE `t_user_1` (`id` bigint(20) NOT NULL COMMENT '用户编号',`name` varchar(255) DEFAULT NULL COMMENT '用户名',`age` int(11) DEFAULT NULL COMMENT '用户年龄',`salary` double DEFAULT NULL COMMENT '用户薪资',`birthday` datetime DEFAULT NULL COMMENT '用户生日',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_user_2` (`id` bigint(20) NOT NULL COMMENT '用户编号',`name` varchar(255) DEFAULT NULL COMMENT '用户名',`age` int(11) DEFAULT NULL COMMENT '用户年龄',`salary` double DEFAULT NULL COMMENT '用户薪资',`birthday` datetime DEFAULT NULL COMMENT '用户生日',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 项目依赖

<?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.zjp</groupId><artifactId>shading-jdbc-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>shading-jdbc-demo</name><description>shading-jdbc-demo</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.17</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.6.13</spring-boot.version></properties><dependencies><!--springboot--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.20</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!--sharding-jdbc--><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.1</version></dependency><!--hutool工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency><!--测试数据--><dependency><groupId>com.github.javafaker</groupId><artifactId>javafaker</artifactId><version>1.0.2</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><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>${spring-boot.version}</version><configuration><mainClass>com.zjp.shadingjdbcdemo.ShadingJdbcDemoApplication</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>

3. 编写配置文件(非Sharding-JDBC配置部分)

server:port: 18080
spring:application:name: sharding-jdbc-demologging:level:com.zjp.shadingjdbcdemo: debug
mybatis-plus:mapper-locations: classpath:mapper/*.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true# 设置当查询结果值为null时,同样映射该查询字段给实体(Mybatis-Plus默认会忽略查询为空的实体字段返回)。call-setters-on-nulls: true

4. 创建实体类

注意:

  1. @TableName 注解指定的表为虚拟表 t_user。
  2. @TableId 的 type 属性为 IdType.AUTO 则采用 Sharding-JDBC 数据源生成策略,否则采用 MyBatis Plus 主键生成策略。
  3. 主键的数据类型为包装类,否则主键赋值失败,为默认值0。
package com.zjp.shadingjdbcdemo.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_user")
public class User implements Serializable {private static final long serialVersionUID = 1L;/*** 用户编号*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 用户名*/private String name;/*** 用户年龄*/private Integer age;/*** 用户薪资*/private Double salary;/*** 用户生日*/private LocalDateTime birthday;
}

5. 编写Service层和mapper层

package com.zjp.shadingjdbcdemo.service;import com.zjp.shadingjdbcdemo.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;public interface UserService extends IService<User> {
}
package com.zjp.shadingjdbcdemo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zjp.shadingjdbcdemo.entity.User;
import com.zjp.shadingjdbcdemo.mapper.UserMapper;
import com.zjp.shadingjdbcdemo.service.UserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
package com.zjp.shadingjdbcdemo.mapper;import com.zjp.shadingjdbcdemo.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {
}

6. 编写测试类

package com.zjp.shadingjdbcdemo;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.javafaker.Faker;
import com.zjp.shadingjdbcdemo.entity.User;
import com.zjp.shadingjdbcdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.hint.HintManager;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDateTime;
import java.time.Year;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Random;@Slf4j
@SpringBootTest
public class ShadingJdbcDemoTests {@Autowiredprivate UserService userService;private static final Faker FAKER = new Faker();/*** 测试精确分片、行表达式分片、复合分片和不分片*/@Testpublic void testSaveUser() {Date birthday = FAKER.date().birthday(18, 70);User user = new User().setName(FAKER.name().fullName()).setAge(Year.now().getValue() - birthday.getYear() - 1900).setSalary(10000.0).setBirthday(birthday.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());userService.save(user);}/*** 测试范围分片*/@Testpublic void testGetUser() {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.ge(User::getId,1L);List<User> list = userService.list(wrapper);list.forEach(System.out::println);}/*** 测试hint分片*/@Testpublic void testHint() {Date birthday = FAKER.date().birthday(18, 100);// 创建一个HintManager对象, 确保线程内只存在一个HintManager对象,否则会抛出异常"Hint has previous value, please clear first."HintManager.clear();HintManager hintManager = HintManager.getInstance();// 根据数据库hint分片规则选取数据库hintManager.addDatabaseShardingValue("t_user", 3);// 根据数据表hint分片规则选取数据表hintManager.addTableShardingValue("t_user", 1);User user = new User().setName(FAKER.name().fullName()).setAge(18).setSalary(10000.0).setBirthday(birthday.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());userService.save(user);// HintManager存放在线程变量中, 所以需要清除HintManager.clear();}
}

7. 测试

在对应的策略上打断点,以debug方式启动测试方法,查看是否走断点。

2.4. 自定义主键生成策略

2.4.1. 源码参考

1. Ctrl + 左键点击 type,点击定位文件位置,如下图所示,这三个文件即为主键生成策略的配置文件。org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator文件指定了两种生成策略,分别为UUID和雪花算法。

2. 以UUID为例,通过实现 ShardingKeyGenerator 接口,重新 getType 方法指定配置文件的文件名称和重新 generateKey 方法自定义主键生成策略来实现主键生成。

2.4.2. 自定义主键生成策略步骤

1. 编写 ShardingKeyGenerator 实现类

package com.zjp.shadingjdbcdemo.strategy.keygen;import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;import java.util.Properties;
import java.util.Random;@Slf4j
@Getter
@Setter
public class MyShardingKeyGenerator implements ShardingKeyGenerator {private Properties properties = new Properties();/*** 重写generateKey方法以生成唯一的键值* 此方法用于生成一个随机的长整型数字作为键值,* 键值范围为1到1000,旨在用于需要唯一标识符的场景** @return 生成的键值作为一个Comparable对象返回,由于键值为Long类型,*         而Long实现了Comparable接口,这使得返回值可以与其它对象进行比较*/@Overridepublic Comparable<?> generateKey() {Random random = new Random();Long id = (long) (random.nextInt(1000) + 1);log.info("自定义主键生成策略,主键为:{}", id);return id;}/*** 重写getType方法* 返回一个预定义的键值,用于标识特定的类型或功能这个方法总是返回"MyKey",* 表示当前对象或方法的特定类型或功能** @return String 表示当前对象或方法类型的预定义键值*/@Overridepublic String getType() {return "MyKey";}
}

2. 在resource目录下创建META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator文件,文件内容为自定义主键生成策略类的全限定名。

com.zjp.shadingjdbcdemo.strategy.keygen.MyShardingKeyGenerator

3. 配置文件指定自定义主键生成策略

spring:shardingsphere:sharding:tables:key-generator:column: id # 主键IDtype: MyKey  # 自定义主键生成策略

4. 如果是MyBatis Plus,则需要将主键生成策略换成IdType.AUTO

package com.zjp.shadingjdbcdemo.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_user")
public class User implements Serializable {private static final long serialVersionUID = 1L;/*** 用户编号*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 用户名*/private String name;/*** 用户年龄*/private Integer age;/*** 用户薪资*/private Double salary;/*** 用户生日*/private LocalDateTime birthday;
}

 5. 编写测试类

package com.zjp.shadingjdbcdemo;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.javafaker.Faker;
import com.zjp.shadingjdbcdemo.entity.User;
import com.zjp.shadingjdbcdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.hint.HintManager;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDateTime;
import java.time.Year;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Random;@Slf4j
@SpringBootTest
public class ShadingJdbcDemoTests {@Autowiredprivate UserService userService;private static final Faker FAKER = new Faker();@Testpublic void testSaveUser() {Date birthday = FAKER.date().birthday(18, 70);User user = new User().setName(FAKER.name().fullName()).setAge(Year.now().getValue() - birthday.getYear() - 1900).setSalary(10000.0).setBirthday(birthday.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());userService.save(user);}/*** 测试范围分片*/@Testpublic void testGetUser() {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.ge(User::getId,1L);List<User> list = userService.list(wrapper);list.forEach(System.out::println);}
}

6. 启动测试

通过日志和数据库可以看出主键生成成功。

 2.5. 广播表

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

2.5.1. 配置步骤

编写配置文件,指定广播表:

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:names: ds0,ds1ds0:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: rootds1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: rootsharding:broadcast-tables: # 广播表规则列表- t_dicttables:t_dict:key-generator:column: dict_idtype: SNOWFLAKEprops: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false

 2.5.2. 测试

1. 创建表结构

CREATE DATABASE IF NOT EXISTS database1;USE database1;DROP TABLE IF EXISTS `t_dict`;CREATE TABLE `t_dict` (`dict_id` bigint(20) NOT NULL COMMENT '字典id',`type` varchar(50) NOT NULL COMMENT '字典类型',`code` varchar(50) NOT NULL COMMENT '字典编码',`value` varchar(50) NOT NULL COMMENT '字典值',PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;CREATE DATABASE IF NOT EXISTS database2;USE database2;DROP TABLE IF EXISTS `t_dict`;CREATE TABLE `t_dict` (`dict_id` bigint(20) NOT NULL COMMENT '字典id',`type` varchar(50) NOT NULL COMMENT '字典类型',`code` varchar(50) NOT NULL COMMENT '字典编码',`value` varchar(50) NOT NULL COMMENT '字典值',PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

2. 创建实体类

package com.zjp.shadingjdbcdemo.entity;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_dict")
public class TDict implements Serializable {private static final long serialVersionUID = 1L;/*** 字典id*/private Long dictId;/*** 字典类型*/private String type;/*** 字典编码*/private String code;/*** 字典值*/private String value;
}

 3. 编写sevice层和mapper层

package com.zjp.shadingjdbcdemo.service;import com.zjp.shadingjdbcdemo.entity.TDict;
import com.baomidou.mybatisplus.extension.service.IService;public interface ITDictService extends IService<TDict> {
}
package com.zjp.shadingjdbcdemo.service.impl;import com.zjp.shadingjdbcdemo.entity.TDict;
import com.zjp.shadingjdbcdemo.mapper.TDictMapper;
import com.zjp.shadingjdbcdemo.service.ITDictService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;@Service
public class TDictServiceImpl extends ServiceImpl<TDictMapper, TDict> implements ITDictService {
}
package com.zjp.shadingjdbcdemo.mapper;import com.zjp.shadingjdbcdemo.entity.TDict;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface TDictMapper extends BaseMapper<TDict> {
}

 4. 编写测试类

package com.zjp.shadingjdbcdemo;import com.zjp.shadingjdbcdemo.entity.TDict;
import com.zjp.shadingjdbcdemo.service.ITDictService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
public class ShadingJdbcDemoTests {@Autowiredprivate ITDictService itDictService;@Testpublic void testSaveDict() {TDict dict = new TDict().setCode("001").setValue("男").setType("sex");itDictService.save(dict);}
}

7. 测试

运行发现两个库的 t_dict 表均插入成功。

2.6. 绑定表

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

SELECT i.*, o.* FROM t_user o JOIN t_user_item i ON o.id = i.user_id WHERE o.id IN (10, 11);

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

SELECT i.*, o.* FROM t_user_1 o JOIN t_user_item_1 i ON o.id = i.user_id WHERE o.id IN (10, 11);
SELECT i.*, o.* FROM t_user_1 o JOIN t_user_item_2 i ON o.id = i.user_id WHERE o.id IN (10, 11);
SELECT i.*, o.* FROM t_user_2 o JOIN t_user_item_1 i ON o.id = i.user_id WHERE o.id IN (10, 11);
SELECT i.*, o.* FROM t_user_2 o JOIN t_user_item_2 i ON o.id = i.user_id WHERE o.id IN (10, 11);

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

SELECT i.*, o.* FROM t_user_1 o JOIN t_user_item_1 i ON o.id = i.user_id WHERE o.id IN (10, 11);
SELECT i.*, o.* FROM t_user_2 o JOIN t_user_item_2 i ON o.id = i.user_id WHERE o.id IN (10, 11);

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

2.6.1. 配置步骤

 编写配置文件,指定绑定表:

spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:names: ds0,ds1ds0:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: rootds1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=falseusername: rootpassword: rootsharding:default-data-source-name: ds0default-database-strategy:inline:sharding-column: idalgorithm-expression: ds$->{id % 2}tables:t_user:actual-data-nodes: ds$->{0..1}.t_user_$->{1..2}key-generator:column: idtype: SNOWFLAKEtable-strategy:inline:sharding-column: idalgorithm-expression: t_user_$->{id % 2 + 1}t_user_item:actual-data-nodes: ds$->{0..1}.t_user_item_$->{1..2}key-generator:column: item_idtype: SNOWFLAKEtable-strategy:inline:sharding-column: user_idalgorithm-expression: t_user_item_$->{user_id % 2 + 1}binding-tables: # 绑定表规则列表- t_user,t_user_itemprops: # 属性配置sql:show: true #是否开启SQL显示,默认值: false# executor.size: #工作线程数量,默认值: CPU核数# max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1# check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false

 2.5.2. 测试

1. 创建表结构

CREATE DATABASE IF NOT EXISTS database1;USE database1;DROP TABLE IF EXISTS `t_user_item_1`;
DROP TABLE IF EXISTS `t_user_item_2`;CREATE TABLE `t_user_item_1` (`item_id` bigint(20) NOT NULL,`user_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_user_item_2` (`item_id` bigint(20) NOT NULL,`user_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE DATABASE IF NOT EXISTS database2;USE database2;DROP TABLE IF EXISTS `t_user_item_1`;
DROP TABLE IF EXISTS `t_user_item_2`;CREATE TABLE `t_user_item_1` (`item_id` bigint(20) NOT NULL,`user_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `t_user_item_2` (`item_id` bigint(20) NOT NULL,`user_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 创建实体类

package com.zjp.shadingjdbcdemo.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;import java.time.LocalDateTime;public class UserItemVo {/*** 用户编号*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 用户名*/private String name;/*** 用户年龄*/private Integer age;/*** 用户薪资*/private Double salary;/*** 用户生日*/private LocalDateTime birthday;private Long itemId;
}

 3. 编写mapper层

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zjp.shadingjdbcdemo.mapper.UserItemMapper"><select id="getList" resultType="com.zjp.shadingjdbcdemo.entity.UserItemVo">SELECT i.*, o.*FROM t_user oJOIN t_user_item i ON o.id = i.user_idWHERE o.id IN (10, 11);</select>
</mapper>

 4. 编写测试类

package com.zjp.shadingjdbcdemo;import com.zjp.shadingjdbcdemo.entity.UserItemVo;
import com.zjp.shadingjdbcdemo.service.UserItemService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@Slf4j
@SpringBootTest
public class ShadingJdbcDemoTests {@Autowiredprivate UserItemService userItemService;@Testpublic void test() {UserItemVo result = userItemService.selectList();log.info("查询结果为:{}", result);}
}

7. 测试

在注释掉spring.shardingsphere.sharding.binding-tables配置项时,日志为:

在配置 spring.shardingsphere.sharding.binding-tables配置项时,日志为:


本篇文章示例代码:GitHub - kerrsixy/sharding-jdbc-demo

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多线程--常见锁策略--Java

目录 一、悲观锁VS乐观锁 1.悲观锁 2.乐观锁 二、重量级锁VS轻量级锁 1.重量级锁 2.轻量级锁 三、自旋锁 1.自旋锁概念 四、公平锁VS非公平锁 1.公平锁 2.非公平锁 3.注意 五、可重入锁和不可重入锁 六、读写锁 1.线程对于数据的访问方式 注意&#xff1a;以下讲…

基于SSM的农家乐管理系统+论文示例参考

1.项目介绍 功能模块&#xff1a;管理员&#xff08;农家乐管理、美食信息管理、住宿信息管理、活动信息、用户管理、活动报名、论坛等&#xff09;&#xff0c;普通用户&#xff08;注册登录、活动报名、客房预订、用户评价、收藏管理、模拟支付等&#xff09;技术选型&#…

jmeter--CSV数据文件设置--请求体设置变量

目录 一、示例 1、准备组织列表的TXT文件&#xff0c;如下&#xff1a; 2、添加 CSV数据文件设置 &#xff0c;如下&#xff1a; 3、接口请求体设置变量&#xff0c;如下&#xff1a; 二、CSV数据文件设置 1、CSV Data Set Config 配置选项说明 2、示例 CSV 文件内容 3、…

Redis环境部署(主从模式、哨兵模式、集群模式)

一、概述 REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库…

【大数据学习 | flume】flume之常见的sink组件

Flume Sink取出Channel中的数据&#xff0c;进行相应的存储文件系统&#xff0c;数据库&#xff0c;或者提交到远程服务器。Flume也提供了各种sink的实现&#xff0c;包括HDFS sink、Logger sink、Avro sink、File Roll sink、HBase sink&#xff0c;。 ​ Flume Sink在设置存…

【ArcGIS微课1000例】0127:计算城市之间的距离

本文讲述,在ArcGIS中,计算城市(以地级城市为例)之间的距离,效果如下图所示: 一、数据准备 加载配套实验数据包中的地级市和行政区划矢量数据(订阅专栏后,从私信查收数据),如下图所示: 二、计算距离 1. 计算邻近表 ArcGIS提供了计算点和另外点之间距离的工具:分析…

(Linux 入门) 基本指令、基本权限

目录 一、什么是操作系统 二、基础指令 01. ls 指令 02. pwd命令 03.mkdir 04. touch指令 05.rmdir指令 && rm 指令 06.man指令&#xff08;重要&#xff09; 07 cat 08.cp指令 09 mv指令 10 alias 指令 11.more指令 12.head指令 13.less指令 14.时间相…

云原生之运维监控实践-使用Prometheus与Grafana实现对Nginx和Nacos服务的监测

背景 如果你要为应用程序构建规范或用户故事&#xff0c;那么务必先把应用程序每个组件的监控指标考虑进来&#xff0c;千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章&#xff0c;当…

QT基础 窗体 对话框 文件 QT5.12.3环境 C++实现

一、堆栈窗体 1. 概念 是一种界面设计思路&#xff0c; 多个窗体重叠在一起&#xff0c;通过点击对应的按钮&#xff0c;显示对应的界面。 2. 相关方法 Public FunctionsQStackedWidget(QWidget * parent 0)//stack如果单纯指定父窗口&#xff0c;但是没有指定大小&#xf…

【NOIP提高组】潜伏者

【NOIP提高组】潜伏者 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; R国和S国正陷入战火之中&#xff0c;双方都互派间谍&#xff0c;潜入对方内部&#xff0c;伺机行动。 历尽艰险后&#xff0c;潜伏于 S 国的R 国间谍小C 终于摸清了S 国…

共享门店模式:创新零售的新篇章

​在消费升级和数字化转型的双重浪潮下&#xff0c;传统零售业正面临前所未有的挑战与机遇。其中&#xff0c;共享门店模式作为一种创新的商业模式&#xff0c;正逐渐成为实体店铺应对电商冲击、提升运营效率和市场竞争力的重要途径。本文将深入解析共享门店模式的内涵、优势、…

除了电商平台,还有哪些网站适合进行数据爬取?

在数字化时代&#xff0c;数据的价值日益凸显&#xff0c;而网络爬虫技术成为获取数据的重要手段。除了电商平台&#xff0c;还有许多其他类型的网站适合进行数据爬取&#xff0c;以支持市场研究、数据分析、内容聚合等多种应用场景。本文将探讨除了电商平台外&#xff0c;还有…

STM32G4的数模转换器(DAC)的应用

目录 概述 1 DAC模块介绍 2 STM32Cube配置参数 2.1 参数配置 2.2 项目架构 3 代码实现 3.1 接口函数 3.2 功能函数 3.3 波形源代码 4 DAC功能测试 4.1 测试方法介绍 4.2 波形测试 概述 本文主要介绍如何使用STM32G4的DAC模块功能&#xff0c;笔者使用STM32Cube工具…

Linux-Apache

文章目录 Apache基础配置 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Linux专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年11月19日12点20分 Apache Web服务器用来实现HTTP和相关TCP连接的处理&#xff0c;同时负责所提供资源的管理…

[JavaWeb] 尚硅谷JavaWeb课程笔记

1 Tomcat服务器 Tomcat目录结构 bin&#xff1a;该目录下存放的是二进制可执行文件&#xff0c;如果是安装版&#xff0c;那么这个目录下会有两个exe文件&#xff1a;tomcat10.exe、tomcat10w.exe&#xff0c;前者是在控制台下启动Tomcat&#xff0c;后者是弹出GUI窗口启动To…

集群聊天服务器(12)nginx负载均衡器

目录 负载均衡器nginx负载均衡器优势 如何解决集群聊天服务器跨服务器通信问题&#xff1f;nginx的TCP负载均衡配置nginx配置 负载均衡器 目前最多只能支持2w台客户机进行同时聊天 所以要引入集群&#xff0c;多服务器。 但是客户连哪一台服务器呢&#xff1f;客户并不知道哪一…

基于YOLOv8深度学习的智慧交通事故评级检测系统研究与实现(PyQt5界面+数据集+训练代码)

本文研究并实现了一种基于YOLOv8深度学习模型的智慧交通事故评级检测系统&#xff0c;旨在解决传统交通事故检测过程中效率低、误报率高等问题。该系统通过深度学习技术的应用&#xff0c;结合交通事故图像的分析&#xff0c;能够实现对事故的精准识别和评级&#xff0c;进而为…

基于Java Springboot出租车管理网站

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

iOS 18 导航栏插入动画会导致背景短暂变白的解决

问题现象 在最新的 iOS 18 系统中,如果我们执行导航栏的插入动画,可能会造成导航栏背景短暂地变为白色: 如上图所示:我们分别向主视图和 Sheet 弹出视图的导航栏插入了消息,并应用了动画效果。可以看到,前者的导航栏背景会在消息插入那一霎那“变白”,而后者则没有任何…

[Go]-sync.map使用详解

sync.Map是 Go 语言中在并发环境下使用的安全映射类型。 一、为什么需要sync.Map 在 Go 语言中&#xff0c;普通的map不是并发安全的。当多个 goroutine 同时读写一个普通map时&#xff0c;可能会导致程序出现未定义的行为&#xff0c;比如数据竞争、程序崩溃等。而sync.Map则…