关于 ShardingSphere
下图是官网的介绍
本篇文章只介绍 ShardingSphere-JDBC 的使用
功能
ShardingSphere-JDBC 的核心功能是数据分片和读写分离,通过 ShardingSphere=JDBC 应用可以透明的使用 JDBC 访问已经分库分表,读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。
在开始使用 SharedingSphere 之前在温习一下分库分表的基本要点:
分库分表的作用
- 加大存储
- 提升查询效率
- 提升数据库并发能力
分库分表策略的选择
- 分库分表一定要提前设计,尽量避免临时设计。只针对最核心的表
- 分库分表后,对表的查询尽量简单,不要有跨表,跨库的复杂查询
- 分库分表尽量同时进行,可以分担网络的压力
核心概念
- 逻辑表:水平拆分后,逻辑和数据结构相同的表的总称。user 被拆分成 user_1 user_2 ,user 就是逻辑表。
- 真实表:在分片数据库中真实存在的物理表,user_1 user_2 就是真实表
- 数据节点:数据分片的最小单元,有数据源名称和数据表组成。
- 绑定表:分片规则一致的主表和子表
- 广播表:也叫公共表,指的是在所有的分片数据源中都存在的表,结构和数据完全一致,一般是配置表,字典表,这种关联次数多,几乎不会发生变化,数据量小的表。
- 分片键:用于分片的数据库字段,是将数据库表水平拆分的关键字段,比如按照 id 取模分表。id 就是分片键,SQL 语句中如果不含分片键,将会执行全路由,性能会很差。
- 分片算法:通过分片算法将数据进行分片,支持通过 = BETWEEN IN 分片,算法需要开发者自行实现,灵活度高。
- 分片策略:真正用于分片操作的是分片键+分片算法,也就是分片策略。在 ShardingSphereJDBC 中,一般采用 Groovy 表达式来描述分片策略。如 inline 策略的描述:user_$->{u_id%8}, 它描述了根据 u_id 模 8,分成 8 张表,表名称是 user_0 到 user_7
分片策略介绍
- inline: 直接在配置文件中通过 Groovy 表达式实现分片策略,如:user_$->{u_id%8}, 只支持精确查找 ( = in),不支持范围查找
- standard: 标准分片策略,用户通过编写分片精确查找和范围查找算法,实现自定义的分片,可以支持精确查找和范围查找,但是分片键只能有一个,也就是只能根据一列来进行分片
- complex: 复合分片,和标准分片一样,但是可以支持多个列对数据进行分片
- hint: 强制路由策略,不再由 SQL 决定如何分片,直接由用户指定分片参数
下面分别介绍这几种分片策略的使用:
准备工作
1. 数据库
准备两个数据库,建议使用 docker-compose 快速搭建
m1 数据源下的 sharing_1 库 m2 数据源下的 sharing_2 数据库
docker-compose.yml:
version: '3.8'
services:
mysql-master:
container_name: mysql-single
image: mysql:5.7.31
restart: always
ports:
- 3342:3306
privileged: true
volumes:
- $PWD/log:/var/log/mysql
- $PWD/my.cnf:/etc/mysql/my.cnf
- $PWD/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: "123456"
command: [
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_general_ci',
'--max_connections=3000'
]
2. 新建 SpringBoot 项目
要用到的依赖如下:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 使用 mybatis plus 简化数据操作,专注 ShardingSphere JDBC 框架的使用-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 需要用到一些日期操作, 借助第三方工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.23</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>
3. 在项目中配置数据源
在 application.properties 中添加数据源的配置:
# 配置多个数据源 m1 m2
spring.shardingsphere.datasource.names=m1,m2
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.11.12:3340/sharding_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.11.12:3342/sharding_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
4. 项目目录结构
inline
1. 新建 course 表
ShardingSphere 不会帮助我们建表,只是将 SQL 解析成符合分表分库之后的实际 SQL
我们使用 course 表举例:
create table course_1
(
cid BIGINT(20) PRIMARY KEY,
cname VARCHAR(50) NOT NULL,
user_id BIGINT(20) NOT NULL,
cstatus varchar(10) NOT NULL
);
create table course_2
(
cid BIGINT(20) PRIMARY KEY,
cname VARCHAR(50) NOT NULL,
user_id BIGINT(20) NOT NULL,
cstatus varchar(10) NOT NULL
);
分别在两个数据库中执行 建表 sql。
2. 指定逻辑表与实际表的关系
在 application.properties 配置文件中添加:
# table.course 指定一个逻辑表是 course, 这个表不用真实存在
# course 是逻辑表, 他下面分布的真实表 course_1 course_2 groovy 表达方式
# course 逻辑表下面有 4 个真实表,分别是 m1:course_1 m1:course_2 m2:course_1 m2:course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 使用雪花算法生成 cid 列的内容
# 下面三行的配置指定了 course 表主键生成的策略,使用了雪花算法。它是一种效率较高的分布式 ID, 算法实现细节自行查阅。
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 雪花算法需要的参数,可以不写
spring.shardingsphere.sharding.tables.course.key-generator.props.worker.id=2
# 分片算法, 选库,这里根据 cid 进行选择
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=cid
# 表达式可能的结果是 m1 m2
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{cid%2+1}
# 分片算法,选表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
# 结果表达式的结果可能是:course_1 course_2
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid%2+1}
先选出所有符合条件的库,然后在每个库下选出复合条件的表。这里一共有四张表 m1:course_1 m1:course_2 m2:course_1 m2:course_2, 我们想要实现的效果是 cid 为奇数的放入 m2:course_2 cid 为偶数的放入 m1:course_1
【疑问】
这种分片下就用不到 m1:course_2 和 m2:course_1, 是不是这两张表就不用建了呢?
不是! 原因可以看下面的实践
3. 编写实体类
如下:
Course.java:
package io.dc.shardingjdbc.model;
import com.baomidou.mybatisplus.annotation.TableId;
/**
* @author dc on 2023/12/4
*/
public class Course {
// 使用雪花算法生成 id 时, 这里要用 Long 而不是 long
@TableId
private Long cid;
private String cname;
private Long userId;
private String cstatus;
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getCstatus() {
return cstatus;
}
public void setCstatus(String cstatus) {
this.cstatus = cstatus;
}
@Override
public String toString() {
return "Course{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", userId=" + userId +
", cstatus='" + cstatus + '\'' +
'}';
}
}
4. 测试插入
在测试类中:
@Test
public void addCourse() {
for (int i = 1; i <= 200; i++) {
Course c = new Course();
// 注意,因为使用了雪花算法生成 cid,这里不要手动设置
// c.setCid(Long.valueOf(i));
c.setCname("xxx");
c.setUserId(Long.valueOf(1000 + i));
c.setCstatus("1");
courseMapper.insert(c);
}
}
执行这个测试方法,控制台部分输出如下:
2023-12-09 18:36:05.455 INFO 22524 --- [main] ShardingSphere-SQL:Logic SQL: INSERT INTO course ( cid, cname, user_id, cstatus ) VALUES ( ?, ?, ?, ? )
2023-12-09 18:36:05.455 INFO 22524 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: INSERT INTO course_2 ( cid, cname, user_id, cstatus ) VALUES (?, ?, ?, ?) ::: [1733435338651176961, xxx, 1001, 1]
插入一条数据产生两条 sql,一条是逻辑 sql,一条是真实 sql,并且真实 sql 是插入到了 m2:course_2 中,cid 是奇数。复合我们的配置,下面看下数据库的数据分布:
可以看到,m1:course_1 cid 都是偶数,m1:course_2 为空 m2:course_1 cid 为空,m2:course_2 都是奇数
5. 测试全表查询
添加测试方法,测试全表查询
@Test
public void queryCourse() {
System.out.println("全表查询");
// mybatis plus 写法
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("cid").orderByDesc("user_id");
List<Course> courseList = courseMapper.selectList(wrapper);
for (Course course : courseList) {
System.out.println(course);
}
}
运行测试方法,控制台部分输出:
全表查询
2023-12-09 18:50:35.066 INFO 17952 --- [main] ShardingSphere-SQL: Logic SQL: SELECT cid,cname,user_id,cstatus FROM course ORDER BY cid DESC,user_id DESC
2023-12-09 18:50:35.067 INFO 17952 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_1 ORDER BY cid DESC,user_id DESC
2023-12-09 18:50:35.067 INFO 17952 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_2 ORDER BY cid DESC,user_id DESC
2023-12-09 18:50:35.067 INFO 17952 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_1 ORDER BY cid DESC,user_id DESC
2023-12-09 18:50:35.067 INFO 17952 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_2 ORDER BY cid DESC,user_id DESC
Course{cid=1733435344938438657, cname='xxx', userId=1200, cstatus='1'}
Course{cid=1733435344904884226, cname='xxx', userId=1199, cstatus='1'}
Course{cid=1733435344883912706, cname='xxx', userId=1198, cstatus='1'}
.... 省略
因为没有用到分片键,产生了四条真实 sql,四张表都查了一遍。这个案例可解上面的疑问,为什么用不到表还要建出来。也许可以调整 groovy 表达式的写法,只需见 2 张表就行了。后面调研一下。
6. 精确查询
添加测试方法:
@Test
public void queryCourseById() {
System.out.println("按照 Id 查询");
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.eq("cid", 1733435340907712513L);
List<Course> courseList = courseMapper.selectList(wrapper);
System.out.println("按照 ID");
for (Course course : courseList) {
System.out.println(course);
}
wrapper.clear();
System.out.println("in 查询");
wrapper.in("cid", Arrays.asList(1733435340953849857L, 1733435340844797954L));
courseList = courseMapper.selectList(wrapper);
for (Course course : courseList) {
System.out.println(course);
}
}
运行测试方法,控制台输出:
按照 Id 查询
2023-12-09 19:19:00.664 INFO 14872 --- [main] ShardingSphere-SQL: Logic SQL: SELECT cid,cname,user_id,cstatus FROM course WHERE (cid = ?)
2023-12-09 19:19:00.664 INFO 14872 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_2 WHERE (cid = ?) ::: [1733435340907712513]
按照 ID
Course{cid=1733435340907712513, cname='xxx', userId=1020, cstatus='1'}
in 查询
2023-12-09 19:19:00.714 INFO 14872 --- [main] ShardingSphere-SQL: Logic SQL: SELECT cid,cname,user_id,cstatus FROM course WHERE (cid IN (?,?))
2023-12-09 19:19:00.714 INFO 14872 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_1 WHERE (cid IN (?,?)) ::: [1733435340953849857, 1733435340844797954]
2023-12-09 19:19:00.714 INFO 14872 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_2 WHERE (cid IN (?,?)) ::: [1733435340953849857, 1733435340844797954]
2023-12-09 19:19:00.714 INFO 14872 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_1 WHERE (cid IN (?,?)) ::: [1733435340953849857, 1733435340844797954]
2023-12-09 19:19:00.714 INFO 14872 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT cid,cname,user_id,cstatus FROM course_2 WHERE (cid IN (?,?)) ::: [1733435340953849857, 1733435340844797954]
Course{cid=1733435340844797954, cname='xxx', userId=1018, cstatus='1'}
Course{cid=1733435340953849857, cname='xxx', userId=1022, cstatus='1'}
按照 Id 查询,只产生了一条 SQL,根据我们设置的分片算法直接定位到 m2:cour2 表。
按照 in 查询,进行了全表路由
7. 范围查询
添加测试方法:
@Test
public void queryCourseByRange() {
System.out.println("按照 Id 范围查询");
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.between("cid", 938561391969701888L, 938561392535932929L);
List<Course> courseList = courseMapper.selectList(wrapper);
for (Course course : courseList) {
System.out.println(course);
}
// 在 inline 策略下,不支持范围查询
}
执行报错:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalStateException: Inline strategy cannot support this type sharding:RangeRouteValue(columnName=cid, tableName=course, valueRange=[938561391969701888‥938561392535932929])
报错信息是 inline 策略不支持范围查询。
由于这个策略不支持范围查询,灵活度远远不够,接下来看支持范围查询的 stander 策略
standard
standard 策略我们用 dept 表验证:
1. 创建表
分别在两个数据库中执行
drop table if exists dept_1;
create table dept_1
(
did BIGINT(20) PRIMARY KEY,
name VARCHAR(10) NOT NULL,
user_num int NOT NULL default 0
);
drop table if exists dept_2;
create table dept_2
(
did BIGINT(20) PRIMARY KEY,
name VARCHAR(10) NOT NULL,
user_num int NOT NULL default 0
);
2. 添加配置
# 指定逻辑表和真实表的对应关系
spring.shardingsphere.sharding.tables.dept.actual-data-nodes=m$->{1..2}.dept_$->{1..2}
# 使用雪花算法生成 did 列的内容
spring.shardingsphere.sharding.tables.dept.key-generator.column=did
spring.shardingsphere.sharding.tables.dept.key-generator.type=SNOWFLAKE
## 标准策略分片
## 分表时选择 did 作为分片键
spring.shardingsphere.sharding.tables.dept.table-strategy.standard.sharding-column=did
# 支持精确查询和范围查询,都需要自己实现分片算法
# 精确查询时选择表
spring.shardingsphere.sharding.tables.dept.table-strategy.standard.precise-algorithm-class-name=io.dc.shardingjdbc.algorithm.DeptTablePreciseAlgorithm
# 范围查询时选择表
spring.shardingsphere.sharding.tables.dept.table-strategy.standard.range-algorithm-class-name=io.dc.shardingjdbc.algorithm.DeptTableRangeAlgorithm
## 分库时选择 did 作为分片键
spring.shardingsphere.sharding.tables.dept.database-strategy.standard.sharding-column=did
# 精确查询时选择库
spring.shardingsphere.sharding.tables.dept.database-strategy.standard.precise-algorithm-class-name=io.dc.shardingjdbc.algorithm.DeptDsPreciseAlgorithm
# 范围查询时选择库
spring.shardingsphere.sharding.tables.dept.database-strategy.standard.range-algorithm-class-name=io.dc.shardingjdbc.algorithm.DeptDsRangeAlgorithm
3. 创建实体类和 Mapper
实体类 Dept.java
package io.dc.shardingjdbc.model;
/**
* @author dc on 2023/12/6
*/
public class Dept {
private Long did;
private Integer userNum;
private String name;
public Long getDid() {
return did;
}
public void setDid(Long did) {
this.did = did;
}
public Integer getUserNum() {
return userNum;
}
public void setUserNum(Integer userNum) {
this.userNum = userNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dept{" +
"did=" + did +
", userNum=" + userNum +
", name='" + name + '\'' +
'}';
}
}
DeptMapper.java
package io.dc.shardingjdbc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.dc.shardingjdbc.model.Dept;
import io.dc.shardingjdbc.model.User;
/**
* @author dc on 2023/12/4
*/
public interface DeptMapper extends BaseMapper<Dept> {
}
4. 算法实现【重点】
假如想要 did 最后一位等于 1 的放到 m1:dept_1 中,其他情况放入 m2:dept_2 中
分库算法:
在配置文件中已经配置 分库算法的类名:DeptDsPreciseAlgorithm
和 DeptDsRangeAlgorithm
, 下面开始实现这两种算法:
DeptDsPreciseAlgorithm.java
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
/**
* @author dc on 2023/12/5
* PreciseShardingAlgorithm<T> 实现精确查询分片算法 T 是分片键的类型
*/
public class DeptDsPreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
*
* @param databases 已经配置的数据源
* @param preciseShardingValue 分片键相关信息, 包含分片键和逻辑表
* @return 从哪个库中查询(插入)。因为这是精确分片,只会返回一个值
*/
@Override
public String doSharding(Collection<String> databases, PreciseShardingValue<Long> preciseShardingValue) {
System.out.println("dept 库精确");
// 获取分片键的值
Long cid = preciseShardingValue.getValue();
if(cid % 10 == 1){
System.out.println("在 m1 库中查找");
return "m1";
}
System.out.println("在 m2 库中查找");
return "m2";
}
}
接下来看范围查询算法实现:
DeptDsRangeAlgorithm.java
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import java.util.Collection;
/**
* @author dc on 2023/12/5
* 实现 RangeShardingAlgorithm<Long> 接口
*/
public class DeptDsRangeAlgorithm implements RangeShardingAlgorithm<Long> {
/**
* @param databases 配置的数据源
* @param rangeShardingValue 分片相关信息
* @return 因为是范围查询,可能要在多个数据源中才能找到所有数据,所以返回值是集合
*/
@Override
public Collection<String> doSharding(Collection<String> databases, RangeShardingValue<Long> rangeShardingValue) {
// 获取范围查询中范围的最小值
Long min = rangeShardingValue.getValueRange().lowerEndpoint();
// 获取范围查询中范围的最大值
Long max = rangeShardingValue.getValueRange().upperEndpoint();
// 获取逻辑表名
String logicTableName = rangeShardingValue.getLogicTableName();
// 获取分片键列的名字
String columnName = rangeShardingValue.getColumnName();
// 范围查询需要在所有的库中查询, 暂时没有想到好的案例,没有用到上面几个变量,仅仅做演示
return databases;
}
}
分表算法
与分库算法差不多
DeptTablePreciseAlgorithm.java
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.math.BigInteger;
import java.util.Collection;
/**
* @author dc on 2023/12/5
*/
public class DeptTablePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
System.out.println("dept 表精确");
Long cid = preciseShardingValue.getValue();
String logicTable = preciseShardingValue.getLogicTableName();
String actualTable = logicTable + "_";
if (cid % 10 == 1) {
actualTable += "1";
System.out.println("在 " + actualTable+ " 中查找");
return actualTable;
}
actualTable += "2";
System.out.println("在 " + actualTable+ " 中查找");
return actualTable;
}
}
DeptTableRangeAlgorithm.java
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import java.util.Collection;
/**
* @author dc on 2023/12/5
*/
public class DeptTableRangeAlgorithm implements RangeShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> tableNames, RangeShardingValue<Long> rangeShardingValue) {
System.out.println("===========range================");
// 范围查询应该在所有表中都查一遍
return tableNames;
}
}
5. 测试插入
添加测试方法:
@Test
public void insertDept() {
for (int i = 0; i < 100; i++) {
Dept dept = new Dept();
dept.setUserNum((i +1) * 200);
dept.setName("测试标准策略_" + i);
deptMapper.insert(dept);
}
}
执行方法后,查看数据库如下:
发现数据分布正如分片算法一致,did 追后一位等于 1 的都在 m1:dept_1,下面测试一下查询
6. 测试精确查询
新建测试方法:
@Test
public void queryDept() {
QueryWrapper<Dept> wrapper = new QueryWrapper<>();
wrapper.eq("did",940635075672801281L);
List<Dept> deptList = deptMapper.selectList(wrapper);
for (Dept dept : deptList) {
System.out.println(dept);
}
}
执行方法,控制台有如下输出:
dept 库精确
在 m1 库中查找
dept 表精确
在 dept_1 中查找
2023-12-10 15:55:33.006 INFO 19080 --- [main] ShardingSphere-SQL: Logic SQL: SELECT did,user_num,name FROM dept WHERE (did = ?)
2023-12-10 15:55:33.006 INFO 19080 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT did,user_num,name FROM dept_1 WHERE (did = ?) ::: [940635075672801281]
Dept{did=940635075672801281, userNum=6000, name='测试标准策略_29'}
走了精确查询算法,所以只产生了一条真实 SQL
7. 测试范围查询
添加测试方法:
@Test
public void queryDeptRange() {
QueryWrapper<Dept> wrapper = new QueryWrapper<>();
wrapper.between("did",940635075672801281L, 940635076750737408L);
List<Dept> deptList = deptMapper.selectList(wrapper);
for (Dept dept : deptList) {
System.out.println(dept);
}
}
执行方法,控制台有如下部分输出:
2023-12-10 16:02:04.018 INFO 23444 --- [main] ShardingSphere-SQL: Logic SQL: SELECT did,user_num,name FROM dept WHERE (did BETWEEN ? AND ?)
2023-12-10 16:02:04.018 INFO 23444 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT did,user_num,name FROM dept_1 WHERE (did BETWEEN ? AND ?) ::: [940635075672801281, 940635076750737408]
2023-12-10 16:02:04.019 INFO 23444 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: SELECT did,user_num,name FROM dept_2 WHERE (did BETWEEN ? AND ?) ::: [940635075672801281, 940635076750737408]
2023-12-10 16:02:04.019 INFO 23444 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT did,user_num,name FROM dept_1 WHERE (did BETWEEN ? AND ?) ::: [940635075672801281, 940635076750737408]
2023-12-10 16:02:04.019 INFO 23444 --- [main] ShardingSphere-SQL: Actual SQL: m2 ::: SELECT did,user_num,name FROM dept_2 WHERE (did BETWEEN ? AND ?) ::: [940635075672801281, 940635076750737408]
Dept{did=940635075672801281, userNum=6000, name='测试标准策略_29'}
Dept{did=940635075714744321, userNum=6400, name='测试标准策略_31'}
Dept{did=940635075756687361, userNum=6800, name='测试标准策略_33'}
Dept{did=940635075924459521, userNum=8400, name='测试标准策略_41'}
Dept{did=940635076134174721, userNum=10800, name='测试标准策略_53'}
Dept{did=940635076176117761, userNum=11200, name='测试标准策略_55'}
Dept{did=940635076301946881, userNum=12800, name='测试标准策略_63'}
产生了 4 条 SQL,符合分片算法
complex
上面两种策略的分片键只能是单个, 复合策略可以支持多个分片键。
用 message 来演示:
1. 创建 message 表
drop table message_1;
create table message_1
(
mid BIGINT(20) PRIMARY KEY,
body VARCHAR(500) NOT NULL,
created_time BIGINT(20) NOT NULL,
created_time_str char(19) NOT NULL,
type BIGINT(20) NOT NULL
);
drop table message_2;
create table message_2
(
mid BIGINT(20) PRIMARY KEY,
body VARCHAR(500) NOT NULL,
type BIGINT(20) NOT NULL,
created_time_str char(19) NOT NULL,
created_time BIGINT(20) NOT NULL
);
drop table message_3;
create table message_3
(
mid BIGINT(20) PRIMARY KEY,
body VARCHAR(500) NOT NULL,
type BIGINT(20) NOT NULL,
created_time_str char(19) NOT NULL,
created_time BIGINT(20) NOT NULL
);
分别在两个数据源中执行
2. 创建实体类和 Mapper
package io.dc.shardingjdbc.model;
import com.baomidou.mybatisplus.annotation.TableId;
/**
* @author dc on 2023/12/6
*/
public class Message {
/**
* 消息 id
*/
@TableId
private Long mid;
/**
* 消息文本
*/
private String body;
/**
* 消息类型
*/
private Long type;
/**
* 消息创建时间
*/
private Long createdTime;
private String createdTimeStr;
public Long getMid() {
return mid;
}
public void setMid(Long mid) {
this.mid = mid;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Long getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Long createdTime) {
this.createdTime = createdTime;
}
public Long getType() {
return type;
}
public void setType(Long type) {
this.type = type;
}
public String getCreatedTimeStr() {
return createdTimeStr;
}
public void setCreatedTimeStr(String createdTimeStr) {
this.createdTimeStr = createdTimeStr;
}
@Override
public String toString() {
return "Message{" +
"mid=" + mid +
", body='" + body + '\'' +
", type=" + type +
", createdTime=" + createdTime +
", createdTimeStr='" + createdTimeStr + '\'' +
'}';
}
}
Mapper:
package io.dc.shardingjdbc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.dc.shardingjdbc.model.Course;
import io.dc.shardingjdbc.model.Message;
public interface MessageMapper extends BaseMapper<Message> {
}
3. 添加分片配置
# 对 message 表进行分表分库, 采用复杂策略
spring.shardingsphere.sharding.tables.message.actual-data-nodes=m$->{1..2}.message_$->{1..3}
spring.shardingsphere.sharding.tables.message.key-generator.column=mid
spring.shardingsphere.sharding.tables.message.key-generator.type=SNOWFLAKE
# 雪花算法需要的参数,可以不写(工作机器编号,生成分布式 id 需要)
spring.shardingsphere.sharding.tables.message.key-generator.props.worker.id=2
# 复杂策略分片
# 分片键
spring.shardingsphere.sharding.tables.message.table-strategy.complex.sharding-columns=type,created_time
spring.shardingsphere.sharding.tables.message.table-strategy.complex.algorithm-class-name=io.dc.shardingjdbc.algorithm.MessageTableComplexSharing
spring.shardingsphere.sharding.tables.message.database-strategy.complex.sharding-columns=type,created_time
spring.shardingsphere.sharding.tables.message.database-strategy.complex.algorithm-class-name=io.dc.shardingjdbc.algorithm.MessageDatabaseComplexSharing
写法和标准策略一样,只是不再区分精确查找,还是范围查找,由算法来负责实现
想实现如下分片功能
- 消息类型 (type) 等于 1,并且 消息创建时间 (created_time) 小于 20231201 放入 m1:message_1 中
- 消息类型 (type) 等于 2,并且 消息创建时间 (created_time) 大于 20231201 放入 m1:message_2 中
- 其他情况放入 m1:message_3
下面直接看分片算法如何实现
4. 分片算法
分库
package io.dc.shardingjdbc.algorithm;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author dc on 2023/12/6
*/
public class MessageDatabaseComplexSharing implements ComplexKeysShardingAlgorithm<Long> {
/**
* 将消息类型为 1 的,并且时间在 2023-12-01 之前的插入 m1 库 message_1 表中
* 将消息类型为 2 的,并且时间在 2023-12-01 之后的插入 m2 库 message_2 表中
* 其他情况数据在 m1 库中的 message_3
*/
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
// 获取精确查找的字段
Collection<Long> typeList = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("type");
Collection<Long> createdTimeList = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("created_time");
// 获取范围类型的字段
Range<Long> typeRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("type");
Range<Long> createdTimeRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("created_time");
Collection<String> databases = new ArrayList<>();
long now = DateUtil.parse("20231201").getTime();
// 两个字段都是精确值
if (!ObjectUtils.isEmpty(typeList) && !ObjectUtils.isEmpty(createdTimeList)) {
System.out.println("选库: type 和 created_time 都是精确查找");
for (Long type : typeList) {
for (Long createTime : createdTimeList) {
if (type.equals(1L) && createTime.compareTo(now) < 0) {
databases.add("m1");
} else if (type.equals(2L) && createTime.compareTo(now) > 0) {
databases.add("m2");
} else {
databases.add("m1");
}
}
}
System.out.println("选中的库有");
for (String database : databases) {
System.out.println(database);
}
return databases;
}
// type 是精确值, created_time 是范围值,如:
// type = 1 and (created_time between ('20231202','20231204'))
if (!ObjectUtils.isEmpty(typeList) && !ObjectUtils.isEmpty(createdTimeRange)) {
System.out.println("选库: type 是精确查找,created_time 是范围查找");
Long min = createdTimeRange.lowerEndpoint();
Long max = createdTimeRange.upperEndpoint();
// 根据分片规则
for (Long type : typeList) {
if (type.equals(1L) && max.compareTo(now) < 0) {
databases.add("m1");
} else if (type.equals(2L) && min.compareTo(now) > 0) {
databases.add("m2");
} else {
databases.add("m1");
}
}
System.out.println("选中的库有");
for (String database : databases) {
System.out.println(database);
}
return databases;
}
System.out.println("选库:其他情况在所有的数据库中查找");
return collection;
}
}
分表
package io.dc.shardingjdbc.algorithm;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author dc on 2023/12/6
* 将消息类型为 1 的,并且时间在 2023-12-01 之前的插入 m1 库 message_1 表中
* 将消息类型为 2 的,并且时间在 2023-12-01 之后的插入 m2 库 message_2 表中
* 其他情况数据在 m1 库中的 message_3
*/
public class MessageTableComplexSharing implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
// 获取精确查找的字段
Collection<Long> typeList = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("type");
Collection<Long> createdTimeList = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("created_time");
// 获取范围类型的字段
Range<Long> typeRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("type");
Range<Long> createdTimeRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("created_time");
Collection<String> tables = new ArrayList<>();
String logicTable = complexKeysShardingValue.getLogicTableName();
long now = DateUtil.parse("20231201").getTime();
// 规则 1: 两个字段都是精确值,数据插入时唯一命中的。因为不可能插入一个范围吧
if (!ObjectUtils.isEmpty(typeList) && !ObjectUtils.isEmpty(createdTimeList)) {
System.out.println("选表: type 和 created_time 都是精确查找");
for (Long type : typeList) {
for (Long createTime : createdTimeList) {
if (type.equals(1L) && createTime.compareTo(now) < 0) {
tables.add(logicTable + "_1");
} else if (type.equals(2L) && createTime.compareTo(now) > 0) {
tables.add(logicTable + "_2");
} else {
tables.add(logicTable + "_3");
}
}
}
System.out.println("选中的表有:");
for (String table : tables) {
System.out.println(table);
}
return tables;
}
// 规则 2: type 是精确值, created_time 是范围值,如:type = 1 and (created_time between ('20231202','20231204'))
if (!ObjectUtils.isEmpty(typeList) && !ObjectUtils.isEmpty(createdTimeRange)) {
System.out.println("选库: type 是精确查找,created_time 是范围查找");
Long min = createdTimeRange.lowerEndpoint();
Long max = createdTimeRange.upperEndpoint();
// 根据分片规则
for (Long type : typeList) {
if (type.equals(1L) && max.compareTo(now) < 0) {
tables.add(logicTable + "_1");
} else if (type.equals(2L) && min.compareTo(now) > 0) {
tables.add(logicTable + "_2");
} else {
tables.add(logicTable + "_3");
}
}
System.out.println("选中的表有:");
for (String table : tables) {
System.out.println(table);
}
return tables;
}
System.out.println("选表:其他情况在所有的数据库中查找");
return collection;
}
}
5. 测试插入
添加测试方法:
@Test
public void insertMessage() {
// 创建时间 分布在 20231201 前后
List<Date> dateList = new ArrayList<>();
dateList.add(DateUtil.parse("20231101"));
dateList.add(DateUtil.parse("20231102"));
dateList.add(DateUtil.parse("20231103"));
dateList.add(DateUtil.parse("20231201"));
dateList.add(DateUtil.parse("20231202"));
dateList.add(DateUtil.parse("20231203"));
dateList.add(DateUtil.parse("20231204"));
for (int i = 0; i < 100; i++) {
Message message = new Message();
message.setBody("测试分库分表" + i);
// type 取值范围 [0 1 2]
message.setType((long) (i % 3));
Date date = dateList.get(i % dateList.size());
message.setCreatedTime(date.getTime());
message.setCreatedTimeStr(DateUtil.format(date, "yyyy-MM-dd HH:mm:ss"));
messageMapper.insert(message);
}
}
执行测试方法,查看数据分布:
可以看出数据分布复合分片算法设置的规则
6. 测试查询
/**
* 符合规则 1,应该只在 m1:message_1 中查找
*/
@Test
public void queryMessageEq() {
QueryWrapper<Message> wrapper = new QueryWrapper<>();
wrapper.eq("type", 1L).eq("created_time", DateUtil.parse("20231102").getTime());
List<Message> messages = messageMapper.selectList(wrapper);
for (Message message : messages) {
System.out.println(message);
}
}
/**
* = 和 in 仍然属于精确查找,type = 1 and created_time in (20231102,20231203)
* 应该在 m1:message_1 和 m1:message_3 查找
*/
@Test
public void queryMessageIn() {
QueryWrapper<Message> wrapper = new QueryWrapper<>();
wrapper.eq("type", 1L);
wrapper.in("created_time", DateUtil.parse("20231102").getTime(), DateUtil.parse("20231203").getTime());
List<Message> messages = messageMapper.selectList(wrapper);
for (Message message : messages) {
System.out.println(message);
}
}
/**
* = 和 in 仍然属于精确查找,type = 2 and created_time in (20231102,20231203)
* 应该在 m2:message_2 和 m1:message_3 查找
* 但是实际输出的 SQL 是 m1:message_2 m1:messsage_3 m2:message_2 m3:message_3
* 我猜测他的工作原理如下:
* 1. 先根据要查找的数据找出数据库: 2 和 20231102 结合得出要在 m1 中查找;2 和 20231203 结合得出要在 m2 中查找
* 2. 再根据要查找的数据找出表: 2 和 20231102 结合得出要在 message_3 中查找;2 和 20231203 结合得出要在 message_2 中查找
* 3. [m1 m2] 和 [message_2 message_3] 交叉结合 即: m1:message_2 m1:message_3 m2:message_2 m2:message_3
* 所以是 4 条 sql。该规则适用于上面 queryMessageIn 方法
*/
@Test
public void queryMessageIn2() {
QueryWrapper<Message> wrapper = new QueryWrapper<>();
wrapper.eq("type", 2L);
wrapper.in("created_time", DateUtil.parse("20231102").getTime(), DateUtil.parse("20231203").getTime());
List<Message> messages = messageMapper.selectList(wrapper);
for (Message message : messages) {
System.out.println(message);
}
}
请读者自行测试
hint
强制路由策略,不再根据 SQL 来选择分表分库,而是直接有用户指定
配置如下:
# 对 user 表采用强制路由策略
spring.shardingsphere.sharding.tables.user.actual-data-nodes=m$->{1..2}.user_$->{1..2}
spring.shardingsphere.sharding.tables.user.key-generator.column=uid
spring.shardingsphere.sharding.tables.user.key-generator.type=snowflake
spring.shardingsphere.sharding.tables.user.key-generator.props.worker.id=2
# 强制路由策略分片,没有分片键,因为它不再依赖 sql 了。在程序中直接指定
spring.shardingsphere.sharding.tables.user.table-strategy.hint.algorithm-class-name=io.dc.shardingjdbc.algorithm.UserTableHintSharding
spring.shardingsphere.sharding.tables.user.database-strategy.hint.algorithm-class-name=io.dc.shardingjdbc.algorithm.UserDatabaseHintSharding
创建表,实体类和 Mapper
drop table if exists user_1;
create table user_1
(
uid BIGINT(20) PRIMARY KEY,
name VARCHAR(10) NOT NULL,
age int NOT NULL,
sex tinyint NOT NULL
);
drop table if exists user_2;
create table user_2
(
uid BIGINT(20) PRIMARY KEY,
name VARCHAR(10) NOT NULL,
age int NOT NULL,
sex tinyint NOT NULL
);
package io.dc.shardingjdbc.model;
import com.baomidou.mybatisplus.annotation.TableId;
/**
* @author dc on 2023/12/6
*/
public class User {
private Long uid;
private Integer sex;
private Integer age;
private String name;
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", sex=" + sex +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
package io.dc.shardingjdbc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.dc.shardingjdbc.model.User;
public interface UserMapper extends BaseMapper<User> {}
分片算法
分库
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import java.util.Collection;
import java.util.Collections;
/**
* @author dc on 2023/12/8
*/
public class UserDatabaseHintSharding implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {
// 获取用户传指定的库,用户可以传入多个参数,所以是集合,这里只取第一个
Integer index = (Integer) hintShardingValue.getValues().toArray()[0];
String database = "m" + index;
System.out.println("强制路由 选中数据库 " + database);
if (collection.contains(database)) {
return Collections.singletonList(database);
}
throw new RuntimeException("未知的数据库:" + database);
}
}
分表
package io.dc.shardingjdbc.algorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import java.util.Collection;
import java.util.Collections;
/**
* @author dc on 2023/12/8
*/
public class UserTableHintSharding implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {
// 获取用户指定的表
Integer index = (Integer) hintShardingValue.getValues().toArray()[0];
String table = hintShardingValue.getLogicTableName() + "_" + index;
if (collection.contains(table)) {
System.out.println("强制路由 选中表 " + table);
return Collections.singletonList(table);
}
throw new RuntimeException("未知的表:" + table);
}
}
测试插入
/**
* 强制路由策略插入数据 age < 50 的插入 m1:user_1 age >= 50 的插入 m2:user_2
*/
@Test
public void insertUser() {
HintManager hintManager1 = HintManager.getInstance();
// 用户直接指定选择哪个库那个表进行插入,查询写法一致,不再编写示例
hintManager1.addTableShardingValue("user", 1);
hintManager1.addDatabaseShardingValue("user", 1);
for (int i = 0; i < 50; i++) {
User user = new User();
user.setAge(i % 50 + 1);
user.setName("测试强制路由_" + i);
user.setSex(i % 2);
userMapper.insert(user);
}
hintManager1.close();
HintManager hintManager2 = HintManager.getInstance();
hintManager2.addTableShardingValue("user", 2);
hintManager2.addDatabaseShardingValue("user", 2);
for (int i = 50; i < 100; i++) {
User user = new User();
user.setAge(i % 50 + 1);
user.setName("测试强制路由_" + i);
user.setSex(i % 2);
userMapper.insert(user);
}
hintManager2.close();
}
执行方法,查看数据分布:
广播表
见名思意,就是在所有的数据库中都会存在的表,它一般是字典表。对他写的操作,会同步到所有库中。配置如下:
# 广播表, 见名思意,就是每个数据库中都会全量存一份的表
# 一般是配置表,表中数据变化比较少,关联次数比较多的表
spring.shardingsphere.sharding.broadcast-tables=dict
# 配置主键生成策略
spring.shardingsphere.sharding.tables.dict.key-generator.column=did
spring.shardingsphere.sharding.tables.dict.key-generator.type=SNOWFLAKE
由于比较简单,不再测试
总结
至此,几种分片策略已经演示完毕。标准和复合策略在项目中使用的最多。
关于分库分表,尽量在系统设计之初做。如果是现有系统,在现有可用的手段用尽之前(缓存,ES)不要考虑分库分表,带来的维护成本将非常大。
读写分离
ShardingSphere 使用读写分离比较简单,只需配置主从关系即可。
配置
# 读写分离配置示例
spring.shardingsphere.datasource.names=m1,s1
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.11.12:3340/sharding_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
spring.shardingsphere.datasource.s1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s1.url=jdbc:mysql://192.168.11.12:3341/sharding_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.s1.username=root
spring.shardingsphere.datasource.s1.password=123456
# ds0 是自定义的主从规则, 同时指定了主库是m1
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m1
# 从库可以有多个,这里只配一个s1
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names[0]=s1
# 设置需要读写分离的表
spring.shardingsphere.sharding.tables.dict.actual-data-nodes=ds0.dict
# 配置主键生成策略
spring.shardingsphere.sharding.tables.dict.key-generator.column=did
spring.shardingsphere.sharding.tables.dict.key-generator.type=SNOWFLAKE
spring.shardingsphere.props.sql.show = true
spring.main.allow-bean-definition-overriding=true
测试插入
/**
* 测试读写分离插入
*/
@Test
public void insertDict() {
Dict dict = new Dict();
dict.setType(1);
dict.setKeyword(1);
dict.setValue("使用账号密码登录");
dictMapper.insert(dict);
dict = new Dict();
dict.setType(1);
dict.setKeyword(2);
dict.setValue("使用短信验证码登录");
dictMapper.insert(dict);
dict = new Dict();
dict.setType(2);
dict.setKeyword(1);
dict.setValue("开启设备绑定");
dictMapper.insert(dict);
dict = new Dict();
dict.setType(2);
dict.setKeyword(2);
dict.setValue("关闭设备绑定");
dictMapper.insert(dict);
}
运行方法,控制台如下输出:
2023-12-10 17:21:28.567 INFO 22224 --- [main] ShardingSphere-SQL: Actual SQL: m1 ::: INSERT INTO dict ( did, type, keyword, value ) VALUES (?, ?, ?, ?) ::: [1733778949163016193, 1, 1, 使用账号密码登录]
可以看到插入只在主库
测试查询
/**
* 测试读写分离查询
*/
@Test
public void queryDict() {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("type", 2).eq("keyword", 2);
List<Dict> dicts = dictMapper.selectList(wrapper);
for (Dict dict : dicts) {
System.out.println(dict);
}
}
控制台输出如下:
2023-12-10 17:23:08.519 INFO 9584 --- [main] ShardingSphere-SQL: Actual SQL: s1 ::: SELECT did,type,keyword,value FROM dict WHERE (type = ? AND keyword = ?) ::: [2, 2]
Dict{did=1733778950933012482, type=2, keyword=2, value='关闭设备绑定'}
可以看出查询只在从库 s1
联系方式: dccmmtop@foxmail.com