ruoyi-vue-plus 引入 ShardingSphere-JDBC 实现分库分表
依赖
boot的依赖官方不维护
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.1</version>
</dependency>
需求
我们对 test_demo 表进行分表 test_demo0, test_demo1,根据 id 字段对2取模
数据源配置
application-dev.yml
datasource:
shardingSphere:
url: jdbc:shardingsphere:classpath:sharding.yml
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
p6spy: false # 避免冲突
sharding.yml
dataSources:
shardingmaster:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxxxx/xxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
username: xxxx
password: xxxxx
sql打印日志框架配置处理
我们配置sharding数据源不开启ps6py,由shardingsphere来处理
props:
sql-show: true
这样就能实现日志灵活打印了
分表策略配置
rules:
- !SHARDING
tables:
test_demo:
actualDataNodes: shardingmaster.test_demo$->{0..1}
tableStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: baomidou_inline
shardingAlgorithms:
baomidou_inline:
type: INLINE
props:
algorithm-expression: test_demo$->{id % 2}
allow-range-query-with-inline-sharding: true
- !SINGLE
tables:
- "*.*"
提一下这里的id还是使用的 mybatisplus 的主键策略
实现代码
建表sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for test_demo
-- ----------------------------
DROP TABLE IF EXISTS `test_demo0`;
CREATE TABLE `test_demo0` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '000000' COMMENT '租户编号',
`dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门id',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
`order_num` int(11) NULL DEFAULT 0 COMMENT '排序号',
`test_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'key键',
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '值',
`version` int(11) NULL DEFAULT 0 COMMENT '版本',
`create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
`del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '测试单表' ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `test_demo1`;
CREATE TABLE `test_demo1` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '000000' COMMENT '租户编号',
`dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门id',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
`order_num` int(11) NULL DEFAULT 0 COMMENT '排序号',
`test_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'key键',
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '值',
`version` int(11) NULL DEFAULT 0 COMMENT '版本',
`create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
`del_flag` int(11) NULL DEFAULT 0 COMMENT '删除标志',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '测试单表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
实体类我们不做修改
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("test_demo")
public class TestDemo extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 部门id
*/
private Long deptId;
/**
* 用户id
*/
private Long userId;
/**
* 排序号
*/
@OrderBy(asc = false, sort = 1)
private Integer orderNum;
/**
* key键
*/
private String testKey;
/**
* 值
*/
private String value;
/**
* 版本
*/
@Version
private Long version;
/**
* 删除标志
*/
@TableLogic
private Long delFlag;
}
List<TestDemo> queryListSharding(TestDemoBo bo);
Boolean insertByBoSharding(TestDemoBo bo);
@Override
@DS("shardingSphere")
public List<TestDemo> queryListSharding(TestDemoBo bo) {
return baseMapper.selectList(buildQueryWrapper(bo));
}
@Override
@DS("shardingSphere")
public Boolean insertByBoSharding(TestDemoBo bo) {
TestDemo add = MapstructUtils.convert(bo, TestDemo.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
// 添加对 id 的查询
lqw.eq(ObjectUtil.isNotNull(bo.getId()), TestDemo::getId, bo.getId());
lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
lqw.orderByAsc(TestDemo::getId);
return lqw;
}
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/sharding")
public class TestDemoShardingController extends BaseController {
private final ITestDemoService testDemoService;
/**
* 查询测试单表列表
*/
@SaCheckPermission("demo:demo:list")
@GetMapping("/list")
public List<TestDemo> list(TestDemoBo bo) {
return testDemoService.queryListSharding(bo);
}
/**
* 新增测试单表
*/
@SaCheckPermission("demo:demo:add")
@PostMapping()
public R<Void> add(@RequestBody TestDemoBo bo) {
return toAjax(testDemoService.insertByBoSharding(bo));
}
}
我们对需要分库分表的方法手动指定 sharding 的数据源即可实现执行分库分表的策略
测试
postman
测试新增
可以看到会被路由到不同的表上
测试全量查询
会去所有分表上去搜索
测试根据id查询
会按照id sharding策略去搜索指定的表
shardingsphere-proxy引入方便dba运维
修改 config-sharding.yaml
schemaName: sharding_db
dataSources:
ds_remote:
url: jdbc:mysql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
username: root
password: xxxx
rules:
- !SHARDING
tables:
test_demo:
actualDataNodes: ds_remote.test_demo$->{0..1}
tableStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: alg_inline_demo_id
shardingAlgorithms:
alg_inline_demo_id:
type: INLINE
props:
algorithm-expression: test_demo$->{id % 2}
可以直接查出来
补充
绑定表
eg,订单的主子表,都需要分片,主表和子表分片的规则和字段需要相同,比如都根据order_id字段
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 );
可以减少关联子表sql执行的数量->路由只会使用主表的路由
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);
->
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);
bindingTables:
- t_order,t_order_item
广播表
通常是字典表->所有分片中维护相同的副本
broadcastTables:
- t_dict