文章目录
环境
JDK17
spring-boot-starter-parent2.7.0
shardingsphere-jdbc5.1.1
数据库表
sql
CREATE TABLE `goods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`number` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `orders_202509` (
`id` bigint NOT NULL,
`create_time` datetime DEFAULT NULL,
`price` decimal(16,4) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `orders_202510` (
`id` bigint NOT NULL,
`create_time` datetime DEFAULT NULL,
`price` decimal(16,4) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `orders_202511` (
`id` bigint NOT NULL,
`create_time` datetime DEFAULT NULL,
`price` decimal(16,4) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `orders_202512` (
`id` bigint NOT NULL,
`create_time` datetime DEFAULT NULL,
`price` decimal(16,4) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
pom.xml
xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qf</groupId>
<artifactId>sharding-jdbc-test2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<name>sharding-jdbc-test2</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<!-- <version>5.1.1</version>-->
<version>5.2.0</version>
</dependency>
<!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- mybatis plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- json序列化 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
</project>
配置
js
server:
port: 18080
mybatis-plus:
global-config:
db-config:
id-type: none
mapper-locations: classpath:/mapper/*.xml
type-aliases-package: com.qf.entity
spring:
main:
#启用Bean覆盖 解决同名的dataSource Bean定义冲突
allow-bean-definition-overriding: true
shardingsphere:
props:
sql-show: true
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
rules:
sharding:
# 表的分片策略
tables:
# 逻辑表的名称
meteorology_device_record:
# 数据节点配置,采用Groovy表达式
actual-data-nodes: ds0.meteorology_device_record_$->{202509..202512}
# 配置策略
table-strategy:
# 用于单分片键的标准分片场景
standard:
sharding-column: detected_time
# 分片算法名字
sharding-algorithm-name: monthly_sharding
key-generate-strategy: # 主键生成策略
column: id # 主键列
key-generator-name: snowflake # 策略算法名称(推荐使用雪花算法)
orders:
# 数据节点配置,采用Groovy表达式
actual-data-nodes: ds0.orders_$->{202509..202512}
# 配置策略
table-strategy:
# 用于单分片键的标准分片场景
standard:
sharding-column: create_time
# 分片算法名字
sharding-algorithm-name: order_sharding
key-generate-strategy: # 主键生成策略
column: id # 主键列
key-generator-name: snowflake # 策略算法名称(推荐使用雪花算法)
sharding-algorithms:
monthly_sharding:
type: CLASS_BASED # 或其他合适类型
props:
strategy: STANDARD
algorithmClassName: com.qf.config.MonthlyShardingAlgorithm
order_sharding:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.qf.config.OrderShardingAlgorithm
key-generators:
snowflake:
type: SNOWFLAKE
logging:
level:
org.apache.shardingsphere: INFO
java
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor mybatisPlusInterceptor() {
PaginationInterceptor interceptor = new PaginationInterceptor();
// 添加分页拦截器
interceptor.setDbType(DbType.MYSQL); // 设置数据库类
return interceptor;
}
}
java
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import java.util.*;
@Slf4j
public class OrderShardingAlgorithm implements StandardShardingAlgorithm<Date> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
Date date = preciseShardingValue.getValue();
String suffix = DateUtil.format(date,"yyyyMM");
for (String tableName : collection) {
if (tableName.endsWith(suffix)) {
return tableName;
}
}
throw new IllegalArgumentException("No matching table found for date: " + date);
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) {
Set<String> result = new LinkedHashSet<>();
// 获取范围边界
Range<Date> valueRange = rangeShardingValue.getValueRange();
// 处理不同类型的范围查询
if (valueRange.hasLowerBound() && valueRange.hasUpperBound()) {
// BETWEEN情况: 处理区间范围
Date lowerDate = valueRange.lowerEndpoint();
Date upperDate = valueRange.upperEndpoint();
// 生成从开始月份到结束月份的所有可能表
Calendar current = Calendar.getInstance();
current.setTime(lowerDate);
Calendar end = Calendar.getInstance();
end.setTime(upperDate);
while (!current.after(end)) {
String suffix = DateUtil.format(current.getTime(), "yyyyMM");
for (String tableName : availableTargetNames) {
if (tableName.endsWith(suffix)) {
result.add(tableName);
break;
}
}
// 移动到下一个月
current.add(Calendar.MONTH, 1);
}
} else if (valueRange.hasLowerBound()) {
// >= 情况: 大于等于某个时间
Date lowerDate = valueRange.lowerEndpoint();
for (String tableName : availableTargetNames) {
String tableSuffix = extractSuffixFromTableName(tableName);
if (tableSuffix != null) {
try {
Date tableDate = DateUtil.parse(tableSuffix, "yyyyMM");
Date lowerBoundDate = DateUtil.parse(DateUtil.format(lowerDate, "yyyyMM"), "yyyyMM");
// 如果表的时间大于等于查询起始时间,则包含该表
if (!tableDate.before(lowerBoundDate)) {
result.add(tableName);
}
} catch (Exception e) {
log.warn("Failed to parse date for table: {}", tableName, e);
}
}
}
} else if (valueRange.hasUpperBound()) {
// <= 情况: 小于等于某个时间
Date upperDate = valueRange.upperEndpoint();
for (String tableName : availableTargetNames) {
String tableSuffix = extractSuffixFromTableName(tableName);
if (tableSuffix != null) {
try {
Date tableDate = DateUtil.parse(tableSuffix, "yyyyMM");
Date upperBoundDate = DateUtil.parse(DateUtil.format(upperDate, "yyyyMM"), "yyyyMM");
// 如果表的时间小于等于查询结束时间,则包含该表
if (!tableDate.after(upperBoundDate)) {
result.add(tableName);
}
} catch (Exception e) {
log.warn("Failed to parse date for table: {}", tableName, e);
}
}
}
}
// 如果结果为空,返回所有表(保守策略)
return result.isEmpty() ? availableTargetNames : result;
}
/**
从表名中提取年月后缀*/
private String extractSuffixFromTableName(String tableName) {
// 假设表名以年月结尾,如 table_202509
int lastIndex = tableName.lastIndexOf("_");
if (lastIndex > 0 && lastIndex < tableName.length() - 1) {
String suffix = tableName.substring(lastIndex + 1);
if (suffix.matches("\\d{6}")) {
// 匹配 yyyyMM 格式
return suffix;
}
}
return null;
}
@Override
public String getType() {
return "";
}
@Override
public Properties getProps() {
return null;
}
@Override
public void init(Properties properties) {
}
}
启动类
java
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
@author qixiansheng
@version 1.0
@data 2025/12/7 11:01
*/
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@MapperScan("com.qf.mapper")
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
实体类
java
import lombok.Data;
/**
@author qixiansheng
@version 1.0
@data 2025/12/18 8:14
*/
@Data
public class Goods {
private Long id;
private String name;
private Integer number;
}
java
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
@author qixiansheng
@version 1.0
@data 2025/12/18 21:31
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Orders implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private BigDecimal price;
private Integer status;
}
mapper
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qf.entity.Goods;
import org.apache.ibatis.annotations.Mapper;
/**
@author qixiansheng
@version 1.0
@data 2025/12/18 8:15
*/
@Mapper
public interface GoodsMapper extends BaseMapper<Goods> {
}
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qf.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
@author qixiansheng
@version 1.0
@data 2025/12/18 21:33
*/
@Mapper
public interface OrderMapper extends BaseMapper<Orders> {
int batchInsert(@Param("list") List<Orders> orders);
IPage<Orders> getPage(@Param("page") Page page, @Param("params") Map<String, Object> params);
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.OrderMapper">
<insert id="batchInsert">
insert into orders(id,create_time,price,status) values
<foreach collection="list" item="item" separator=",">
(#{item.id},#{item.createTime},#{item.price},#{item.status})
</foreach>
</insert>
<select id="getPage" resultType="com.qf.entity.Orders">
select *
from orders
<where>
<if test="params.createTime != '' and params.createTime != null">
and create_time = #{params.createTime}
</if>
</where>
</select>
</mapper>
controller
java
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qf.entity.Goods;
import com.qf.entity.Orders;
import com.qf.mapper.GoodsMapper;
import com.qf.mapper.OrderMapper;
import com.qf.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.*;
/**
@author qixiansheng
@version 1.0
@data 2025/12/18 21:31
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private OrderMapper orderMapper;
@Autowired
private GoodsMapper goodsMapper;
/**
给每张表插入数据
@return
*/
@GetMapping("/add")
public Boolean add() {
Date date = DateUtil.parse("2025-08-01 21:00:00");
List<Orders> orders = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
Date offset = DateUtil.offsetMonth(date, i);
for (int j = 0; j < 4; j++) {
Orders order = new Orders();
order.setId(IdUtil.getSnowflake(1, 1).nextId());
order.setCreateTime(DateUtil.offsetDay(offset, j));
order.setPrice(new BigDecimal(j));
order.setStatus(1);
orders.add(order);
}
}
orderMapper.batchInsert(orders);
return true;
}
@GetMapping("/getPage")
public String getPage(@RequestParam Map<String, Object> params) {
Page page = new Page<>(Integer.parseInt(Optional.ofNullable(params.get("page")).orElse("1").toString()),
Integer.parseInt(Optional.ofNullable(params.get("limit")).orElse("5").toString()));
// Page reuslt = orderMapper.selectPage(page, new LambdaQueryWrapper<Orders>());
IPage<Orders> result = orderMapper.getPage(page,params);
return JSON.toJSONString(result);
}
@GetMapping("/getPage2")
public String getPage2(@RequestParam Map<String, Object> params) {
Page page = new Page<>(Integer.parseInt(Optional.ofNullable(params.get("page")).orElse("1").toString()),
Integer.parseInt(Optional.ofNullable(params.get("limit")).orElse("5").toString()));
Page reuslt = goodsMapper.selectPage(page, new LambdaQueryWrapper<Goods>());
return JSON.toJSONString(reuslt);
}
@GetMapping("/getList")
public String getList(@RequestParam Map<String, Object> params) {
Date date = DateUtil.parse("2025-09-01 21:00:00");
List<Orders> orders1 = orderMapper.selectList(new LambdaQueryWrapper<Orders>()
.eq(Orders::getCreateTime, date));
Date dateStart = DateUtil.parse("2025-09-01 00:00:00");
Date dateEnd = DateUtil.parse("2025-10-30 23:59:59");
List<Orders> orders2 = orderMapper.selectList(new LambdaQueryWrapper<Orders>()
.ge(Orders::getCreateTime, date));
List<Orders> orders3 = orderMapper.selectList(new LambdaQueryWrapper<Orders>()
.le(Orders::getCreateTime, dateEnd));
List<Orders> orders4 = orderMapper.selectList(new LambdaQueryWrapper<Orders>()
.ge(Orders::getCreateTime, dateStart)
.le(Orders::getCreateTime, dateEnd));
List<Orders> orders5 = orderMapper.selectList(new LambdaQueryWrapper<Orders>()
.between(Orders::getCreateTime, dateStart, dateEnd));
return JSON.toJSONString(orders1);
}
@GetMapping("/getList2")
public String getList2(@RequestParam Map<String, Object> params) {
List<Goods> list = goodsMapper.selectList(new LambdaQueryWrapper<Goods>());
return JSON.toJSONString(list);
}
}