mx-tableshard 是一个基于 Spring Boot 3 + MyBatis / MyBatis-Plus 的轻量级分表组件。
组件通过 MyBatis 拦截器自动识别实体类上的 @TableShard 注解,从 SQL 参数中提取分片字段值,并将逻辑表名替换为实际物理表名。
- 支持 Spring Boot 自动装配。
- 支持 MyBatis / MyBatis-Plus 查询和更新 SQL 拦截。
- 支持实体类注解声明分表规则。
- 内置三种分表策略:
HashShardStrategy:一致性 Hash 分表。ModShardStrategy:取模分表。DateShardStrategy:日期归档分表。
- 支持从多种参数形态中提取分片值:
- 普通实体对象。
Map参数。- MyBatis
ParamMap。 - MyBatis-Plus
Wrapper。 - MyBatis
ParameterMapping。 - SQL 条件中的
?占位符。
- 日期分片支持按年、月、日、季度生成表名后缀。
- SQL 分片字段条件支持:
=IN (...)LIKE ?><>=<=
- JDK 21+
- Spring Boot 3.x
- MyBatis Spring Boot Starter 3.x
- MyBatis-Plus 3.5.x(可选,使用 MyBatis-Plus 时需要)
当前项目版本:
<groupId>com.mx</groupId>
<artifactId>mx-tableshard</artifactId>
<version>1.0-SNAPSHOT</version>如果在本地项目中使用,先安装到本地 Maven 仓库:
mvn clean install业务项目中引入依赖:
<dependency>
<groupId>com.mx</groupId>
<artifactId>mx-tableshard</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>组件默认启用。
shendu:
shard:
enabled: true如需关闭分表组件:
shendu:
shard:
enabled: false自动装配入口:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
以订单表为例,逻辑表名为 t_order,按月份进行日期分表:
CREATE TABLE t_order_202406 (
id BIGINT PRIMARY KEY,
order_no VARCHAR(64),
user_id BIGINT,
create_time DATETIME
);
CREATE TABLE t_order_202407 (
id BIGINT PRIMARY KEY,
order_no VARCHAR(64),
user_id BIGINT,
create_time DATETIME
);业务 SQL 中仍然写逻辑表名:
SELECT * FROM t_order WHERE create_time >= ?组件会在执行前替换为:
SELECT * FROM t_order_202406 WHERE create_time >= ?import com.baomidou.mybatisplus.annotation.TableName;
import annotation.org.wf.mx.tableshard.TableShard;
import strategy.org.wf.mx.tableshard.DateShardStrategy;
import java.time.LocalDateTime;
@TableName("t_order")
@TableShard(tableName = "t_order", shardColumn = "create_time", strategy = DateShardStrategy.class,
shardPattern = "yyyyMM")
public class Order {
private Long id;
private String orderNo;
private Long userId;
private LocalDateTime createTime;
// getter / setter
}import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface OrderMapper extends BaseMapper<Order> {
}正常使用 MyBatis / MyBatis-Plus 查询即可:
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(Order::getCreateTime, LocalDateTime.of(2024, 6, 1, 0, 0));
List<Order> orders = orderMapper.selectList(wrapper);若 createTime = 2024-06-01T00:00,则会路由到:
t_order_202406
@TableShard(
tableName = "t_order",
shardNum = 4,
shardColumn = "order_no",
strategy = HashShardStrategy.class,
shardPattern = ""
)| 属性 | 说明 | 必填 | 默认值 |
|---|---|---|---|
tableName |
逻辑表名。为空时尝试从 @TableName 或 MyBatis-Plus 表信息中获取。 |
否 | "" |
shardNum |
分表数量。Hash / Mod 策略需要配置。 | 否 | 0 |
shardColumn |
分片字段名,支持驼峰和下划线匹配。 | 是 | 无 |
strategy |
分表策略类。 | 否 | HashShardStrategy.class |
shardPattern |
日期分片格式,主要用于 DateShardStrategy。 |
否 | "" |
适合订单号、用户 ID 等希望分布相对均匀的字段。
@TableShard(
tableName = "t_order",
shardNum = 4,
shardColumn = "order_no",
strategy = HashShardStrategy.class
)
public class Order {
}表名示例:
t_order_1
t_order_2
t_order_3
t_order_4
适合分片数量固定、分片键分布相对均匀的场景。
@TableShard(
tableName = "t_user",
shardNum = 8,
shardColumn = "user_id",
strategy = ModShardStrategy.class
)
public class User {
}表名示例:
t_user_1
t_user_2
...
t_user_8
适合日志、订单、流水等按时间归档的数据。
@TableShard(
tableName = "t_log",
shardColumn = "create_time",
strategy = DateShardStrategy.class,
shardPattern = "yyyyMM"
)
public class LogRecord {
}支持的 shardPattern:
| 模式 | 说明 | 示例日期 | 物理表名 |
|---|---|---|---|
yyyy |
按年分表 | 2024-06-15 |
t_log_2024 |
yyyyMM |
按月分表 | 2024-06-15 |
t_log_202406 |
yyyyMMdd |
按日分表 | 2024-06-15 |
t_log_20240615 |
yyyyQ |
按季度分表 | 2024-05-15 |
t_log_2024q2 |
日期分片值支持:
LocalDateTimeLocalDatejava.util.Date- 毫秒时间戳
Long - 日期字符串,例如:
2024-06-152024-06-15 10:30:002024-06-15T10:30:00
分片字段可以出现在以下条件中:
WHERE order_no = ?
WHERE order_no IN (?)
WHERE create_time > ?
WHERE create_time >= ?
WHERE create_time < ?
WHERE create_time <= ?
WHERE create_time >= #{startTime}
WHERE create_time <= #{endTime}日期分片示例:
SELECT * FROM t_order WHERE status = ? AND create_time >= ?参数:
status = 1
create_time = 2024-06-01T00:00
shardPattern = "yyyyMM" 时路由到:
t_order_202406
组件当前会根据提取到的一个分片值计算一个物理表名。
例如:
WHERE create_time >= '2024-06-30'
AND create_time < '2024-07-02'如果按月分表,真实数据可能分布在:
t_order_202406
t_order_202407
当前组件不会自动生成 UNION ALL,也不会自动查询多个物理表。此类跨分片范围查询需要业务方自行拆分,或在后续版本中支持多表路由。
当前组件没有引入完整 SQL Parser,分片字段定位主要基于 SQL 字符串规则匹配。复杂 SQL 场景需谨慎验证,例如:
WHERE DATE(create_time) >= ?
WHERE ? <= create_time
WHERE EXISTS (SELECT 1 FROM ...)实现 ShardStrategy 接口:
import strategy.org.wf.mx.tableshard.ShardStrategy;
import org.springframework.stereotype.Component;
@Component
public class TenantShardStrategy implements ShardStrategy {
@Override
public String computeTableName(String logicTableName, int shardNum, String shardKey) {
int index = Math.abs(shardKey.hashCode()) % shardNum + 1;
return logicTableName + "_" + index;
}
}实体类中使用自定义策略:
@TableShard(
tableName = "t_tenant_order",
shardNum = 16,
shardColumn = "tenant_id",
strategy = TenantShardStrategy.class
)
public class TenantOrder {
}自定义策略注册为 Spring Bean 后,组件会通过 ShardStrategyRegistry 自动发现并使用。
运行全部测试:
mvn test运行日期分片比较操作符 Demo:
mvn -Dtest=DateShardComparisonDemoTest testDemo 文件:
src/test/java/com/mx/tableshard/demo/DateShardComparisonDemoTest.java
运行 SQL 参数定位测试:
mvn -Dtest=ShardSqlParameterLocatorTest test测试文件:
src/test/java/com/mx/tableshard/extractor/ShardSqlParameterLocatorTest.java
src/main/java/com/mx/tableshard
├── annotation # @TableShard 注解
├── config # Spring Boot 自动配置与配置属性
├── extractor # 分片值提取链路
├── interceptor # MyBatis 分表拦截器
├── strategy # 分表策略
└── utils # 工具类
核心执行流程:
MyBatis 执行 SQL
↓
ShardTableInterceptor 拦截
↓
解析 Mapper 对应实体类上的 @TableShard
↓
ShardValueExtractor 提取分片字段值
↓
ShardStrategy 计算物理表名
↓
替换 SQL 中的逻辑表名
↓
继续执行替换后的 SQL
示例:
SELECT * FROM t_order WHERE create_time >= ?替换后:
SELECT * FROM t_order_202406 WHERE create_time >= ?