ShardingSphere 入门教程之六

大纲

前言

学习资源

ShardingSphere-JDBC 使用

水平分库使用案例

广播表使用案例

提示

本节将演示 SpringBoot + MyBatis-Plus 如何整合 ShardingSphere-JDBC,并使用广播表。完整的案例代码可以直接从 GitHub 下载对应章节 sharding-sphere-lesson-06

核心概念
  • 广播表的简介:

    • 广播表是指所有的分片数据源中都存在的表,且表结构及其表数据(包括主键值)在每个数据库中均完全一致。
    • 适用于数据量不大,且需要与海量数据的表进行关联查询(JOIN)的场景,例如:字典表。
  • 广播表的特性:

    • 数据变更会同步到所有分片节点
      • 对广播表执行 INSERTUPDATEDELETE 操作时,ShardingSphere-JDBC 会将变更操作同时广播到所有数据源,确保各节点的数据完全一致。
    • 查询操作可在任意节点完成
      • 广播表的数据在所有节点中一致,因此查询时可从任意一个数据源获取结果(通常基于负载均衡策略选择节点)。
    • 可与任意表进行 JOIN
      • 广播表在所有节点都有一份完整数据,不涉及跨节点聚合,因此可以安全、方便地与任意分片表或非分片表进行 JOIN 操作。
准备工作
版本说明

本案例所使用的组件版本如下表所示:

组件版本要求
JDK11
MySQL8.0.29
SpringBoot2.7.18
ShardingSphere-JDBC5.1.1
数据库规划

本案例是在两个 MySQL 数据库上实现的,数据库的规划如下图所示:

数据库服务器 IP 端口库的名称表的名称
订单数据库服务器一(server-order0192.168.2.1913310db_ordert_dict
订单数据库服务器二(server-order1192.168.2.1913311db_ordert_dict
数据库部署
  • 部署订单数据库服务器一(server-order0
1
2
3
4
5
6
7
8
9
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3310:3306 \
-v /data/server/order0/conf:/etc/mysql/conf.d \
-v /data/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order0 \
--restart always \
mysql:8.0.29
  • 更改订单数据库服务器一(server-order0)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

  • 部署订单数据库服务器二(server-order1
1
2
3
4
5
6
7
8
9
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3311:3306 \
-v /data/server/order1/conf:/etc/mysql/conf.d \
-v /data/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
--restart always \
mysql:8.0.29
  • 更改订单数据库服务器二(server-order1)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
数据库初始化
  • 在订单数据库服务器一(server-order0)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建数据库
CREATE DATABASE db_order;

-- 切换数据库
USE db_order;

-- 创建表
CREATE TABLE t_dict(
id BIGINT,
dict_type VARCHAR(200),
dict_code VARCHAR(100),
dict_value VARCHAR(200),
PRIMARY KEY(id)
);
  • 在订单数据库服务器二(server-order1)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建数据库
CREATE DATABASE db_order;

-- 切换数据库
USE db_order;

-- 创建表
CREATE TABLE t_dict(
id BIGINT,
dict_type VARCHAR(200),
dict_code VARCHAR(100),
dict_value VARCHAR(200),
PRIMARY KEY(id)
);
案例代码
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<shardingsphere.version>5.1.1</shardingsphere.version>
<mysql-connector.version>8.2.0</mysql-connector.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- SpringBoot -->
<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>

<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- ShardingSphere-Jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<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>
</dependencies>
创建实体类
  • 字典实体类(特别注意:由于广播表的表结构及其表数据(包括主键值)在每个数据库中均完全一致,因此可以使用 MyBatis-Plus 内置的 ASSIGN_ID(基于雪花算法)主键生成策略来生成 ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName("t_dict") // 真实表名
@Data
public class Dict {

/**
* 使用 MyBatis-Plus 的雪花算法来生成 ID <br>
* 由于广播表的表结构及其表数据(包括主键值)在每个数据库中均完全一致,因此可以使用 MyBatis-Plus 的 ASSIGN_ID(基于雪花算法)主键生成策略
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;

/**
* 字典类型 <br>
* 例如: gender / order_status
*/
private String dictType;

/**
* 字典编码 <br>
* 例如: male / pending
*/
private String dictCode;

/**
* 字典显示值 <br>
* 例如: 男 / 待支付
*/
private String dictValue;

}
创建 Mapper
  • 字典 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.Dict;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DictMapper extends BaseMapper<Dict> {

}
配置数据分片
  • 创建配置文件(application.properties),配置数据分片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ----------基础配置----------

# 应用名称
spring.application.name=sharging-jdbc-demo
# 开发环境设置
spring.profiles.active=dev
# 模式配置(可选值:Memory、Standalone、Cluster)
spring.shardingsphere.mode.type=Memory
# 打印SQL语句
spring.shardingsphere.props.sql-show=true

# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(此处的数据节点可以省略不配置,默认情况下,会向所有数据源广播),这里有两个库(server-order0、server-order1),每个库都有一张表(t_dict)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由数据源名 + 表名组成,以小数点分隔;多个表以逗号分隔,支持行表达式
# <table-name>:逻辑表名
spring.shardingsphere.rules.sharding.tables.t_dict.actual-data-nodes=server-order$->{0..1}.t_dict

# ----------广播表配置----------
spring.shardingsphere.rules.sharding.broadcast-tables[0]=t_dict
测试代码
插入数据测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootTest
class ShardingTest {

@Autowired
DictMapper dictMapper;

/**
* 水平分库之广播表:往每个数据库服务器中的 t_dict 表同时插入数据
*/
@Test
public void testInsertBroadcast() {
Dict dict = new Dict();
dict.setDictType("gender");
dict.setDictCode("male");
dict.setDictValue("男");
dictMapper.insert(dict);
}

}

测试代码运行后的输出结果如下(可以发现,往不同数据库服务器中的 t_dict 表插入数据时,该数据的主键(ID)值都是相同的):

1
2
3
4
2020-09-11 23:10:46.695  INFO 15426 --- [           main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES ( ?, ?, ?, ? )
2020-09-11 23:10:46.695 INFO 15426 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2020-09-11 23:10:46.695 INFO 15426 --- [ main] ShardingSphere-SQL : Actual SQL: server-order0 ::: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES (?, ?, ?, ?) ::: [1999512256900829185, gender, male, 男]
2020-09-11 23:10:46.696 INFO 15426 --- [ main] ShardingSphere-SQL : Actual SQL: server-order1 ::: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES (?, ?, ?, ?) ::: [1999512256900829185, gender, male, 男]
查询数据测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
class ShardingTest {

@Autowired
DictMapper dictMapper;

/**
* 水平分库之广播表:查询 t_dict 表中的所有数据,只从任意一个节点查询数据(基于随机负载均衡规则)
*/
@Test
public void testSelectBroadcast() {
List<Dict> dicts = dictMapper.selectList(null);
}

}

测试代码运行后的输出结果如下(可以发现,最终只会从任意一个节点查询数据,默认是基于随机负载均衡规则):

1
2
3
2020-09-11 23:12:36.145  INFO 16016 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT  id,dict_type,dict_code,dict_value  FROM t_dict
2020-09-11 23:12:36.147 INFO 16016 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
2020-09-11 23:12:36.147 INFO 16016 --- [ main] ShardingSphere-SQL : Actual SQL: server-order0 ::: SELECT id,dict_type,dict_code,dict_value FROM t_dict

分页查询使用案例

概念介绍