文章目录
- 一、Sharding-JDBC的应用场景
- 二、SpringBoot 集成 Sharding-JDBC
- 2.1、前期准备
- 2.2、导入`pom.xml`依赖包
- 2.3、结构代码实现
- 2.3.1、MybatisPlusConfig(分页插件)
- 2.3.2、TOrder(订单对象)
- 2.3.3、TOrderMapper(订单mapper接口)
- 2.3.4、TOrderServiceImpl(订单接口实现)
- 2.3.5、ShardingApplication(启动类)
- 2.3.6、application.yml(这个配置文件中只配置公用信息,具体分库分表信息通过active指定)
- 2.3.7、SpringBoot测试类(用于测试所有应用场景,内容后面补充)
- 三、Sharding-JDBC常用场景
- 3.1、读写分离
- 3.1.1、准备读写分离配置文件 application-duxiefenli.yml
- 3.1.2、测试代码
- 3.1.3、测试读写分离效果
- 3.2、读写分离强制路由主库
- 3.2.1、准备读写分离配置文件 application-duxiefenli.yml
- 3.2.2、测试代码
- 3.1.3、测试未开启强制路由主库同一个事务中 查询->更新->查询
- 3.1.4、测试开启强制路由主库同一个事务中 查询->更新->查询
- 3.3、分表(根据user_id取模分表)
- 3.3.1、准备分片表
- 3.3.2、准备分表配置文件 application-fenbiao-qumo.yml
- 3.3.3、测试代码
- 3.3.4、测试分表效果
- 3.4、分表 & 配置读写分离
- 3.4.1、准备分表 & 配置读写分离配置文件 application-duxiefenli-fenbiao-qumo.yml
- 3.5、分库分表
- 3.5.1、准备两个库在每个库中怎么两张表
- 3.5.2、准备分库分表配置文件 application-fenkufenbiao-qumo.yml
- 3.5.3、测试代码
- 3.5.4、测试分库分表效果
- 3.6、自定义分表策略(通过时间分表)
- 3.6.1、准备分片表
- 3.6.2、自定义分表策略代码实现
- 3.6.2.1、精确分片策略实现
- 3.6.2.2、范围区间分片策略实现
- 3.6.3、准备自定义分表配置文件 application-zidingyi-fenbiao-date.yml
- 3.6.4、测试代码
- 3.6.4、测试自定义分表策略效果
- 3.6.4.1、插入数据查看分片表选择
- 3.6.4.2、根据时间精确查询查看分片表选择
- 3.6.4.2、根据时间范围查询查看分片表选择
- 四、总结
一、Sharding-JDBC的应用场景
Sharding-JDBC
是针对分库分表后的操作简化,相当于增强版的JDBC
驱动,常被用于实现应用层读写分离以及分库分表,具体概括可以查看官网这里不做过多说明。
官方文档
二、SpringBoot 集成 Sharding-JDBC
2.1、前期准备
要想比较好的体验Sharding-JDBC
能力最好使用一主多从数据库模式,我这里会使用MySQL一主两从,如果嫌比较麻烦也可以直接使用一主也行,区别不是很大,再执行日志中都能看出区别。
想要部署MySQL一主两从可以参考:Linux从零部署MySQL8.0 主从复制(一主两从)
- 创建一个测试库
sharding-jdbc-test
然后新增一张测试表后续测试都会使用
CREATE TABLE `t_order` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
- 代码结构(这里会使用到Mybatis-plus方便进行测试)
2.2、导入pom.xml
依赖包
我这里使用SpringBoot2.x
sharding-jdbc4.x
不同大版本可能会有冲突,如果测试时发现有冲突先检测一下代码是否有问题,如果代码没有问题可以调整一下包的版本。
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version></parent><properties><hutool.version>5.2.5</hutool.version><fastjson.version>1.2.74</fastjson.version><lombok.version>1.18.12</lombok.version><common.version>1.0-SNAPSHOT</common.version><commons-lang3.version>3.10</commons-lang3.version><druid.version>1.1.10</druid.version><mybatis-plus.version>3.5.1</mybatis-plus.version><sharding-jdbc.version>4.0.0</sharding-jdbc.version></properties><dependencies><!--工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><optional>true</optional></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang3.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--Mysql依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency><!--druid数据源驱动 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>${sharding-jdbc.version}</version></dependency></dependencies>
2.3、结构代码实现
2.3.1、MybatisPlusConfig(分页插件)
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}
2.3.2、TOrder(订单对象)
@Data
public class TOrder implements Serializable {private static final long serialVersionUID = 1L;/** 订单id */@TableId(value = "id",type = IdType.AUTO)private Long id;/** 订单id (通过雪花算法生成)*/private Long orderId;/** 订单编号 */private String orderNo;/** 用户id */private Integer userId;/** 商品信息 */private String goodsInfo;/** 收件地址 */private String toAddress;/** 创建时间 */private Date createTime;
}
2.3.3、TOrderMapper(订单mapper接口)
public interface TOrderMapper extends BaseMapper<TOrder> {@Select("SELECT t1.*,t2.`name` FROM t_order t1 LEFT JOIN t_user t2 ON t1.user_id = t2.id;")List<Map<String,Object>> queryOrderList();
}
- ITOrderService(订单接口)
public interface ITOrderService extends IService<TOrder> {void updateOrder(Long id, String toAddress);
}
2.3.4、TOrderServiceImpl(订单接口实现)
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {@Override@Transactional(rollbackFor = Exception.class)public void updateOrder(Long id, String toAddress){// 强制路由主库// HintManager.getInstance().setMasterRouteOnly();TOrder order = this.getById(id);if(order == null){System.out.println("订单不存在");return;}this.lambdaUpdate().eq(TOrder::getId, id).set(TOrder::getToAddress, toAddress).update();order = this.getById(id);}
}
2.3.5、ShardingApplication(启动类)
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
@MapperScan("com.kerwin.service.mapper")
public class ShardingApplication {public static void main(String[] args) {SpringApplication.run(ShardingApplication.class);}
}
2.3.6、application.yml(这个配置文件中只配置公用信息,具体分库分表信息通过active指定)
server:port: 9090spring:profiles:active: duxiefenli
# active: fenbiao-qumo
# active: duxiefenli-fenbiao-qumo
# active: fenkufenbiao-qumo
# active: zidingyi-fenbiao-datemain:allow-bean-definition-overriding: truedatasource:druid:validation-query: SELECT 1 #验证数据库服务可用性的查询SQL,一般设置为SELECT 1initial-size: 10 #初始连接 默认0max-active: 20 #最大连接数 默认8min-idle: 10 # 最小连接数 默认0max-wait: 5000 # 获取连接最大等待时间,单位毫秒 默认-1一直等待logging:level:com.baomidou: debugcom.kerwin: debugmybatis-plus:configuration:#开启详细日志log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# Mybatis 一级缓存,默认为 SESSION 开启一级缓存,STATEMENT 关闭一级缓存# SESSION session 级别缓存,同一个 session 相同查询语句不会再次查询数据库localCacheScope: STATEMENT# Mybatis 二级缓存,默认为 truecacheEnabled: false
2.3.7、SpringBoot测试类(用于测试所有应用场景,内容后面补充)
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingJDBCTest {@Autowiredprivate ITOrderService orderService;@Autowiredprivate TOrderMapper orderMapper;
}
三、Sharding-JDBC常用场景
Sharding-JDBC
有多种使用场景,可以通过配置文件灵活配置读写分离、分库分表等,这里会对一下常用方式做配置说明。
3.1、读写分离
3.1.1、准备读写分离配置文件 application-duxiefenli.yml
# 分表配置
spring:shardingsphere:datasource:# 全部数据源名称 多个用逗号隔开names:master1,slave1,slave2# 主数据源master1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456# 从数据源slave1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.191:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456slave2:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.192:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456masterslave:# 读写分离配置 用于配置从库负载均衡算法类型,可选值:ROUND_ROBIN(轮询),RANDOM(随机)load-balance-algorithm-type: round_robin# 最终的数据源名称name: dataSource# 主库数据源名称master-data-source-name: master1# 从库数据源名称列表,多个逗号分隔slave-data-source-names: slave1,slave2props:# 开启SQL显示,默认falsesql:show: true
3.1.2、测试代码
//----------------------------- 读写分离测试 start -----------------------------@Testpublic void duxiefenliTest(){// 1、测试自动主库写操作(在执行写操作时会自动选择主库)for (int i = 0; i < 10; i++) {TOrder tOrder = new TOrder();tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));tOrder.setUserId(RandomUtil.randomInt(10));tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));tOrder.setToAddress("地址"+RandomUtil.randomString(5));tOrder.setCreateTime(new Date());orderService.save(tOrder);}// 2、测试从库自动轮询读操作TOrder tOrder1 = orderService.getById(1);TOrder tOrder2 = orderService.getById(2);}//----------------------------- 读写分离测试 end -----------------------------
3.1.3、测试读写分离效果
这里直接运行测试类中的读写分离测试方法,运行之后拉到最下面可以看到插入数据时使用的数据源是DataSources: master1
,而查询时使用的数据源是DataSources: slave1
DataSources: slave2
,多次查看可以看到会对两个从数据源进行轮询。
3.2、读写分离强制路由主库
3.2.1、准备读写分离配置文件 application-duxiefenli.yml
配置文件和读写分离配置文件一致
3.2.2、测试代码
//----------------------------- 读写分离强制路由主库测试 start -----------------------------@Testpublic void duxiefenliMasterRouteTest(){// 测试一个事务中有写也有读操作数据源路由情况orderService.updateOrder(1L,"罗马");}//----------------------------- 读写分离强制路由主库测试 end -----------------------------
3.1.3、测试未开启强制路由主库同一个事务中 查询->更新->查询
这里可以看到第一次查询路由到了从库,第二次查询路由到了主库,在同一个事务中如果进行了修改操作那么后面都会路由主库。
3.1.4、测试开启强制路由主库同一个事务中 查询->更新->查询
打开强制路由主库HintManager.getInstance().setMasterRouteOnly();
运行后可以看到第一次查询也是使用的主库
3.3、分表(根据user_id取模分表)
3.3.1、准备分片表
这里准备两个分片表,t_order_0 t_order_1
CREATE TABLE `t_order_0` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_1` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
3.3.2、准备分表配置文件 application-fenbiao-qumo.yml
# 取模分表配置
spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:# 全部数据源名称 多个用逗号隔开names:master1# 主数据源master1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456props:# 开启SQL显示,默认falsesql:show: true# 分表配置sharding:tables:t_order:# 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式# ds0代表数据源名称,t_order表名称 ,$->{0..1} inline表达式代表取值0 1actual-data-nodes: master1.t_order_$->{0..1}table-strategy:# 分表策略为inlineinline:# 分表列名sharding-column: user_id# 分表算法 根据t_order表中的user_id取模2进行数据分片存储algorithm-expression: t_order_$->{user_id % 2}
3.3.3、测试代码
//----------------------------- 分表测试 start -----------------------------@Testpublic void fenbiaoTest(){// 1、测试自动主库写操作(在执行写操作时会自动选择主库)for (int i = 0; i < 10; i++) {TOrder tOrder = new TOrder();tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));tOrder.setUserId(RandomUtil.randomInt(10));tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));tOrder.setToAddress("地址"+RandomUtil.randomString(5));tOrder.setCreateTime(new Date());orderService.save(tOrder);}// 2、测试根据userId分表查询List<TOrder> list = orderService.lambdaQuery().eq(TOrder::getUserId, 7).list();}//----------------------------- 分表测试 end -----------------------------
3.3.4、测试分表效果
先将application.yml
里的active
配置成fenbiao-qumo
,这里可以观察到新增和查询都能根据user_id
自动取模匹配对应表
3.4、分表 & 配置读写分离
这里只有配置文件不同,其它逻辑和分表一致,只用将application.yml
里的active
配置成duxiefenli-fenbiao-qumo
。
3.4.1、准备分表 & 配置读写分离配置文件 application-duxiefenli-fenbiao-qumo.yml
# 读写分离+取模分表
spring:main:allow-bean-definition-overriding: truedatasource:druid:access-to-underlying-connection-allowed: trueshardingsphere:datasource:# 全部数据源名称 多个用逗号隔开names:master1,slave1,slave2# 主数据源master1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456# 从数据源slave1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.191:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456slave2:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.192:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456props:# 开启SQL显示,默认falsesql:show: truesharding:# 读写分离配置master-slave-rules:ds0:master-data-source-name: master1slave-data-source-names: slave1, slave2# 负载均衡算法 ROUND_ROBIN:轮询 RANDOM:随机load-balance-algorithm-type: ROUND_ROBIN# 分表配置tables:t_order:# 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式# ds0代表数据源名称,t_order表名称 ,$->{0..1} inline表达式代表取值0 1actual-data-nodes: ds0.t_order_$->{0..1}table-strategy:# 分表策略为inlineinline:# 分表列名sharding-column: user_id# 分表算法 根据t_order表中的user_id取模2进行数据分片存储algorithm-expression: t_order_$->{user_id % 2}
3.5、分库分表
3.5.1、准备两个库在每个库中怎么两张表
我这里安装了两MySQL,在两个MySQL中都创建一个叫sharding-jdbc-test
的库,然后在库中创建两个分片表,t_order_0 t_order_1
,也可以在一个MySQL中创建两个数据库测试效果是一样的。
CREATE TABLE `t_order_0` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_1` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
3.5.2、准备分库分表配置文件 application-fenkufenbiao-qumo.yml
spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:# 全部数据源名称 多个用逗号隔开names:master0,master1# 主数据源 数据源名称不能有下划线可以用 -master0:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456master1:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.182:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456props:# 开启SQL显示,默认falsesql:show: truesharding:# 分库配置default-database-strategy:inline:# 分库依据字段sharding-column: user_id# 分库规则algorithm-expression: master$->{user_id % 2}# 分表配置tables:t_order:# 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式# master代表数据源名称, $->{0..1} inline表达式代表取值0 1,user_info表名称 ,$->{0..1} inline表达式代表取值0 1actual-data-nodes: master$->{0..1}.t_order_$->{0..1}table-strategy:# 分表策略为inlineinline:# 分表列名sharding-column: order_id# 分表算法 根据t_order表中的order_id取模2进行数据分片存储algorithm-expression: t_order_$->{order_id % 2}# 自定义t_order中order_id生成,使用雪花算法key-generator:column: order_idtype: SNOWFLAKEprops:worker:# 雪花算法的workId 机器为标识 0-1024id: 996
3.5.3、测试代码
//----------------------------- 分库分表测试 start -----------------------------@Testpublic void fenkuFenbiaoTest(){// 1、测试自动主库写操作(在执行写操作时会自动选择主库)for (int i = 0; i < 10; i++) {TOrder tOrder = new TOrder();tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));tOrder.setUserId(RandomUtil.randomInt(10));tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));tOrder.setToAddress("地址"+RandomUtil.randomString(5));tOrder.setCreateTime(new Date());orderService.save(tOrder);}// 2、测试根据userId分表查询
// List<TOrder> list = orderService.lambdaQuery().eq(TOrder::getUserId, 7).list();}//----------------------------- 分表测试 end -----------------------------
3.5.4、测试分库分表效果
先将application.yml
里的active
配置成fenkufenbiao-qumo
,执行后可以看到根据我们配置的规则进行了分库分表。
3.6、自定义分表策略(通过时间分表)
Sharding-JDBC
提供了表达式配置分表,同时也提供了自定义分表策略方式,不同业务的分表方案不同,常见的有取模、根据时间分表,这里会进行自定义时间分表策略演示,每个月分一张表。
3.6.1、准备分片表
这里准备两张表t_order_2024_7 t_order_2024_8
,用来演示自定义分表策略。
CREATE TABLE `t_order_2024_7` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `t_order_2024_8` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id',`order_id` bigint DEFAULT NULL COMMENT '订单id(通过雪花算法生成)',`order_no` varchar(100) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户id',`goods_info` varchar(100) DEFAULT NULL COMMENT '商品信息',`to_address` varchar(100) DEFAULT NULL COMMENT '收件地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
);
3.6.2、自定义分表策略代码实现
分表策略有两个,精确分片PreciseShardingAlgorithm
,范围区间分片RangeShardingAlgorithm
,这里会对订单创建时间进行两种分片策略实现。
3.6.2.1、精确分片策略实现
@Slf4j
public class OrderCreateTimePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {@Overridepublic String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {System.out.println("table PreciseShardingAlgorithm ");// 真实节点availableTargetNames.stream().forEach((item) -> {log.info("actual node table:{}", item);});log.info("logic table name:{},rout column:{}", shardingValue.getLogicTableName(), shardingValue.getColumnName());//精确分片log.info("column value:{}", shardingValue.getValue());String tb_name = shardingValue.getLogicTableName() + "_";// 根据当前日期 来 分库分表Date date = shardingValue.getValue();String year = String.format("%tY", date);String mon =String.valueOf(Integer.parseInt(String.format("%tm", date))); // 去掉前缀0// 选择表tb_name = tb_name + year + "_" + mon;System.out.println("tb_name:" + tb_name);for (String each : availableTargetNames) {System.out.println("t_order_:" + each);if (each.equals(tb_name)) {return each;}}throw new IllegalArgumentException();}
}
3.6.2.2、范围区间分片策略实现
@Slf4j
public class OrderCreateTimeRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {ArrayList<String> result = new ArrayList<>();//获取表名称String tb_name = rangeShardingValue.getLogicTableName() + "_";//获取查询时间Range<Date> valueRange = rangeShardingValue.getValueRange();Integer queryYearMonthBegin = null; // 查询开始年月if(valueRange.hasLowerBound()){Date beginDate = valueRange.lowerEndpoint();queryYearMonthBegin = Integer.valueOf(String.format("%tY", beginDate)+String.format("%tm", beginDate));}Integer queryYearMonthEnd = null; // 查询结束年月if(valueRange.hasUpperBound()){Date endDate = valueRange.upperEndpoint();queryYearMonthEnd = Integer.valueOf(String.format("%tY", endDate)+String.format("%tm", endDate));}//筛选需要查询的表for (String each : collection) {// 将表中的年月取出来用于判断String yearMonth = each.replace(tb_name, "");String[] yearMonthSplit = yearMonth.split("_");String year = yearMonthSplit[0];String mon = yearMonthSplit[1];mon = mon.length()==1?"0"+mon:mon;Integer yearMonthInt = Integer.parseInt(year+mon); // 数据表对应年月// 当范围查询时为封闭区间 且表的年月在查询区间内if(valueRange.hasLowerBound() && valueRange.hasUpperBound()){if(yearMonthInt>=queryYearMonthBegin && yearMonthInt<=queryYearMonthEnd){result.add(each);}}// 当范围查询时为封闭区间if(valueRange.hasLowerBound() && !valueRange.hasUpperBound()){if(yearMonthInt>=queryYearMonthBegin){result.add(each);}}// 当范围查询时为封闭区间if(!valueRange.hasLowerBound() && valueRange.hasUpperBound()){if(yearMonthInt<=queryYearMonthEnd){result.add(each);}}}if(result.size()==0){log.error("查询表不存在");throw new IllegalArgumentException();}return result;}
}
3.6.3、准备自定义分表配置文件 application-zidingyi-fenbiao-date.yml
# 自定义时间分表配置
# 自定义时间分表配置
spring:main:allow-bean-definition-overriding: trueshardingsphere:datasource:# 全部数据源名称 多个用逗号隔开names:master0# 主数据源master0:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.8.181:3306/sharding-jdbc-test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: 123456props:# 开启SQL显示,默认falsesql:show: true#工作线程数量,默认值: CPU核数executor:size: 6# 分表配置sharding:tables:t_order:# 由数据源名.表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式# master代表数据源名称,t_order表名称 ,$->{7..8} inline表达式代表取值7 8actual-data-nodes: master0.t_order_$->{2024..2024}_$->{7..8}table-strategy:# 分表策略为 自定义策略standard:# 分表列名sharding-column: create_time# 自定义精确分表算法类路径precise-algorithm-class-name: com.kerwin.config.OrderCreateTimePreciseShardingAlgorithm# 自定义范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器range-algorithm-class-name: com.kerwin.config.OrderCreateTimeRangeShardingAlgorithm# 自定义t_order中order_id生成,使用雪花算法key-generator:column: order_idtype: SNOWFLAKEprops:worker:# 雪花算法的workId 机器为标识 0-1024id: 996
3.6.4、测试代码
//----------------------------- 自定义分表策略测试 start -----------------------------@Testpublic void zidinyiFenbiaoTest(){// 1、插入数据for (int i = 0; i < 10; i++) {TOrder tOrder = new TOrder();tOrder.setOrderNo("NO"+RandomUtil.randomNumbers(10));tOrder.setUserId(RandomUtil.randomInt(10));tOrder.setGoodsInfo("商品"+RandomUtil.randomString(5));tOrder.setToAddress("地址"+RandomUtil.randomString(5));tOrder.setCreateTime(new Date());orderService.save(tOrder);}
// // 2、根据时间精确查询
// List<TOrder> list = orderService.lambdaQuery()
// .eq(TOrder::getCreateTime, DateUtil.parseDate("2024-08-07 00:00:00"))
// .list();
//
// // 3、根据时间范围查询
// List<TOrder> list1 = orderService.lambdaQuery()
// .ge(TOrder::getCreateTime, DateUtil.parseDate("2024-07-01 00:00:00"))
// .le(TOrder::getCreateTime, DateUtil.parseDate("2024-08-30 23:59:59"))
// .list();}//----------------------------- 自定义分表策略测试 end -----------------------------
3.6.4、测试自定义分表策略效果
3.6.4.1、插入数据查看分片表选择
执行插入数据可以观察到选择到了t_order_2024_8
表。
3.6.4.2、根据时间精确查询查看分片表选择
根据时间精确查询可以观察到选择到了t_order_2024_8
表。
3.6.4.2、根据时间范围查询查看分片表选择
根据时间范围查询可以观察到选择到了t_order_2024_7 t_order_2024_8
表。
四、总结
这里只演示了部分情况,还有像多表关联查询、分页查询等都是可以适配的,在分页查询中有个问题,如果在分库或分表的情况下因为数据在不同表中获取结果集是有问题的,因为普通的分页查询没法确定多张表的顺序问题,比如一个表有两个分片,查询第3页每页3条数据,如果没有做分表那么在数据库一共会扫描9条数据取出第7-9条,而在分表的情况下则不能只取出每个分片中的第7-9条数据,必须将1-9条数据都取出来进行应用层归并处理,Sharding-JDBC
已经将这个功能实现并且进行了优化,虽然做了优化但是不可避免分页查询还是存在的性能问题,要想解决这个问题也有很多方法这里不做展开。