开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情
话不多说,直接开始。
一、引入依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
二、yml配置
包含数据源配置、数据节点配置、分片策略配置、主键生成策略等配置
server:
port: 9088
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: root
ds1:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: root
enabled: true
mode:
type: memory
props:
sql-show: true
rules:
sharding:
default-key-generate-strategy:
column: id
key-generators:
id-key:
type: SNOWFLAKE
tables:
# 需要分表的逻辑表名, 如果需要配置多个数据表,按照这个规则配置就可以
test_data:
# 表的真实表列表(需要注意这里的表都应该存在,否则就有可能出现我第一篇文章里面提到的 NPE 异常)
actual-data-nodes: ds0.test_data_20220${8..9}, ds0.test_data_2022${10..12}
# 分片策略配置
table-strategy:
standard:
# 分片算法是 his_month_sharding, 这个名称是在下面配置的
sharding-algorithm-name: his_month_sharding
# 分片列是 acquisition_time, 需要注意的是,分表算法的数据类型一定要和这个分片的列数据类型一致
sharding-column: create_time
# 配置分片算法
sharding-algorithms:
# 分片算法名称
his_month_sharding:
# 分片算法类型,这个type就是我们的分片算法实现类中 getType() 的返回值,SPI适用于这种方式
type: HIS_DATA_SPI_BASED
三、实现分片算法
这里主要标准分片算法接口,官方保留了接口,具体算法需要自己动手实现。通过分片键create_time实现范围查找和精确查找。
@Slf4j
public class TstDataMonthShardingAlgorithm implements StandardShardingAlgorithm<Date> {
private final ThreadLocal<SimpleDateFormat> formatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMM"));
private Properties props;
/**
* 设置该参数的原因是,如果在范围查找的时候我们没有设置最小值,比如下面的查询
* where acquisition_time < '2022-08-11 00:00:00'
* 这个时候范围查找就只有上限而没有下限,这时候就需要有一个下限值兜底,不能一致遍历下去
*/
private Date tableLowerDate;
/**
*
* @param availableTargetNames 可用的表列表(配置文件中配置的 actual-data-nodes会被解析成 列表被传递过来)
* @param shardingValue 精确的值
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
Date value = shardingValue.getValue();
// 根据精确值获取路由表
String actuallyTableName = shardingValue.getLogicTableName() + shardingSuffix(value);
if (availableTargetNames.contains(actuallyTableName)) {
return actuallyTableName;
}
return null;
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {
// 获取到范围查找的最小值,如果条件中没有最小值设置为 tableLowerDate
Date rangeLowerDate;
if (shardingValue.getValueRange().hasLowerBound()) {
rangeLowerDate = shardingValue.getValueRange().lowerEndpoint();
} else {
rangeLowerDate = tableLowerDate;
}
// 获取到范围查找的最大值,如果没有配置最大值,设置为当前时间 + 1 月
// 这里需要注意,你的项目里面这样做是否合理
Date rangeUpperDate;
if (shardingValue.getValueRange().hasUpperBound()) {
rangeUpperDate = shardingValue.getValueRange().upperEndpoint();
} else {
// 往后延一个月
rangeUpperDate = DateUtil.offsetMonth(new Date(), 1);
}
rangeUpperDate = DateUtil.endOfMonth(rangeUpperDate);
List<String> tableNames = new ArrayList<>();
// 过滤那些存在的表
while (rangeLowerDate.before(rangeUpperDate)) {
String actuallyTableName = shardingValue.getLogicTableName() + shardingSuffix(rangeLowerDate);
if (availableTargetNames.contains(actuallyTableName)) {
tableNames.add(actuallyTableName);
}
rangeLowerDate = DateUtil.offsetMonth(rangeLowerDate, 1);
}
return tableNames;
}
/**
* sharding 表后缀 _yyyyMM
*/
private String shardingSuffix(Date shardingValue) {
return "_" + formatThreadLocal.get().format(shardingValue);
}
/**
* SPI方式的 SPI名称,配置文件中配置的时候需要用到
*/
@Override
public String getType() {
return "HIS_DATA_SPI_BASED";
}
@Override
public void init(Properties properties) {
this.props = properties;
String autoCreateTableLowerDate = properties.getProperty("auto-create-table-lower");
try {
this.tableLowerDate = formatThreadLocal.get().parse(autoCreateTableLowerDate);
} catch (Exception e) {
log.error("parse auto-create table lower date failed: {}, use default date 202208", e.getMessage());
try {
this.tableLowerDate = formatThreadLocal.get().parse("202208");
} catch (ParseException ignored) {
}
}
}
@Override
public Properties getProps() {
return this.props;
}
}
四、使用SPI注册服务
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
1、在resources目录下新建META-INF/services目录
-- resources
-- META-INF
-- services
2、在这个目录下新建一个与上述接口的全限定名一致的文件
3、在这个文件中写入接口的实现类的全限定名
五、操作尝试
1、新增数据,根据createTime放入不同的表 { "name":"8月5", "createTime":"2022-09-05 12:00:00" }
2、根据ID=1600390521566187522查询数据
3、根据日期区间查询