目录
[三、核心配置文件(application.yml)- 极简规范版](#三、核心配置文件(application.yml)- 极简规范版)
[4.1 包结构(规范,避免混乱)](#4.1 包结构(规范,避免混乱))
[4.2 启动类(核心,开启定时任务)](#4.2 启动类(核心,开启定时任务))
[4.3 MyBatis-Plus分页配置(双库自动适配)](#4.3 MyBatis-Plus分页配置(双库自动适配))
[4.4 实体类(分库分表核心,dbType字段是关键)](#4.4 实体类(分库分表核心,dbType字段是关键))
[4.4.1 位置表实体类(Position)](#4.4.1 位置表实体类(Position))
[4.4.2 日志表实体类(Log)](#4.4.2 日志表实体类(Log))
[4.5 Mapper层(零代码,直接继承BaseMapper)](#4.5 Mapper层(零代码,直接继承BaseMapper))
[4.5.1 PositionMapper](#4.5.1 PositionMapper)
[4.5.2 LogMapper](#4.5.2 LogMapper)
[4.6 Service层(业务代码零分表逻辑)](#4.6 Service层(业务代码零分表逻辑))
[4.6.1 PositionService](#4.6.1 PositionService)
[4.6.2 LogService](#4.6.2 LogService)
[4.7 定时任务(每月自动创建分表,无需人工干预)](#4.7 定时任务(每月自动创建分表,无需人工干预))
[5.1 为什么要关闭Spring默认数据源自动配置?](#5.1 为什么要关闭Spring默认数据源自动配置?)
[5.2 分库路由原理(自动区分MySQL/PostgreSQL)](#5.2 分库路由原理(自动区分MySQL/PostgreSQL))
[5.3 多表分表扩展(新增分表只需3步)](#5.3 多表分表扩展(新增分表只需3步))
[5.4 生产环境优化建议(必看)](#5.4 生产环境优化建议(必看))
前言 :在Java后端开发中,位置数据、日志数据等大流量数据,常需要与主业务数据分库存储,同时按时间分表(如按月分表)降低单表压力。本文基于JDK17+Druid1.2.27+SpringBoot3+MyBatis-Plus+ShardingSphere5 技术栈,实现「主库MySQL(存业务数据)+ 从库PostgreSQL(存位置表+日志表,均按月分表)」的分库分表方案,全程零侵入业务代码、配置简洁规范、可直接复制上线,同时适配多表分表扩展,新手也能快速上手。
本文核心亮点:
-
✅ 配置极简规范:ShardingSphere独立配置,不与原生datasource冲突,可读性拉满
-
✅ 多表分表支持:同时实现Position位置表、Log日志表按月分表,规则独立互不干扰
-
✅ 双库适配:主库MySQL(业务)+ 从库PostgreSQL(大流量数据),自动路由
-
✅ 自动建表:定时任务每月自动创建下个月分表,无需人工干预
-
✅ 零配置类:无需手动编写DataSourceConfig,框架自动接管数据源
-
✅ 双库分页:MyBatis-Plus分页自动适配MySQL/PostgreSQL,无需额外配置
一、技术栈版本确认(必看,避免版本冲突)
本文所有代码均严格适配以下版本,无任何冲突,直接复制即可使用:
| 技术栈 | 版本 | 说明 |
|---|---|---|
| JDK | 17 | SpringBoot3 最低要求,适配现代系统 |
| SpringBoot | 3.2.5 | 官方稳定版,兼容JDK17 |
| Druid | druid-spring-boot-3-starter 1.2.27 | 用户指定版本,SpringBoot3专属,高性能连接池 |
| MyBatis-Plus | 3.5.6 | 支持SpringBoot3,简化CRUD,自带分页 |
| ShardingSphere-JDBC | 5.5.0 | 生产级稳定版,分库分表核心,无中间件损耗 |
| MySQL | 8.0 | 主库,存储主业务数据(如用户、订单) |
| PostgreSQL | 14+ | 从库,存储位置、日志等大流量分表数据 |
二、Maven依赖配置(完整无缺失)
直接复制到pom.xml,无需修改,已排除冲突依赖,适配所有版本要求:
XML
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!-- SpringBoot3 父依赖,统一管理版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- 不使用本地相对路径,优先从中央仓库下载 -->
</parent>
<groupId>com.example</groupId>
<artifactId>sharding-multi-table-demo</artifactId>
<version>1.0.0</version>
<name>sharding-multi-table-demo</name>
<description>多表分库分表完整实践(Position+Log)</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 1. SpringBoot Web 核心依赖(必要,提供Web能力) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 2. MyBatis-Plus 核心依赖(简化CRUD,支持分页) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 3. ShardingSphere JDBC 分库分表核心依赖(无中间件,客户端直连) -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.5.0</version>
</dependency>
<!-- 4. Druid 连接池(用户指定版本,SpringBoot3专属) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.27</version>
</dependency>
<!-- 5. MySQL 8 驱动(主库使用) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope> <!-- 运行时依赖,编译不依赖 -->
</dependency>
<!-- 6. PostgreSQL 驱动(从库使用) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 7. Lombok 简化代码(无需写get/set/toString) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional> <!-- 可选依赖,不影响打包 -->
</dependency>
<!-- 8. 测试依赖(用于单元测试,可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- SpringBoot 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- 排除Lombok依赖,避免打包冗余 -->
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
三、核心配置文件(application.yml)- 极简规范版
最关键的配置,单文件整合所有配置,不拆分、不重复,每个配置项都带详细注释,新手也能看懂,直接复制替换数据库地址/账号密码即可使用:
TypeScript
# ==============================================
# 项目名称:多表分库分表实践(Position+Log)
# 核心架构:主库MySQL(业务)+ 从库PostgreSQL(位置+日志)
# 分表规则:位置表、日志表均按月分表(表名格式:xxx_yyyyMM)
# 连接池:Druid 1.2.27 | 框架:SpringBoot3 + MyBatis-Plus + ShardingSphere5
# ==============================================
# 1. 服务基础配置(端口、上下文路径等)
server:
port: 8080 # 服务端口,可根据实际需求修改
servlet:
context-path: /sharding-demo # 上下文路径,避免与其他服务冲突(可选)
# 2. Spring 全局配置(核心:关闭默认数据源自动配置)
spring:
# 必须关闭!原因:所有数据源交给ShardingSphere统一管理,避免Spring自动加载单数据源导致冲突
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# ===================== ShardingSphere 分库分表核心配置 =====================
# 独立模块,不与其他配置混淆,结构清晰,便于后期维护和扩展
shardingsphere:
# 分库分表总开关:true=开启,false=关闭(开发/生产均建议开启)
enabled: true
# 全局属性配置(日志、元数据检查等)
props:
# 开发环境开启SQL打印(便于调试,查看分表路由是否正确),生产环境建议关闭
sql-show: true
# 简化SQL打印格式,不输出冗余堆栈信息,提升可读性
sql-simple: true
# 开启表元数据检查(确保所有分表结构一致,避免分表字段缺失导致查询异常)
check-table-metadata-enabled: true
# 多数据源配置(主库MySQL + 从库PostgreSQL,均使用Druid连接池)
datasource:
# 定义逻辑数据源名称(多个数据源用逗号分隔,可无限扩展)
names: master,slave
# -------------------- 主库:MySQL(存储主业务数据,如用户、订单) --------------------
master:
# 数据源类型:指定使用Druid连接池(必须与依赖中Druid版本匹配)
type: com.alibaba.druid.pool.DruidDataSource
# MySQL驱动类(MySQL8.0+专用,不可用旧版com.mysql.jdbc.Driver)
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接地址(替换为自己的MySQL地址、数据库名)
url: jdbc:mysql://localhost:3306/business_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true
# 数据库账号(替换为自己的MySQL账号)
username: root
# 数据库密码(替换为自己的MySQL密码)
password: 123456
# Druid连接池参数(生产通用配置,根据业务流量调整)
druid:
initial-size: 5 # 初始化连接数(启动时创建的连接数量)
min-idle: 5 # 最小空闲连接数(空闲时保持的最少连接数,避免频繁创建连接)
max-active: 20 # 最大活跃连接数(最大可同时使用的连接数,防止连接耗尽)
max-wait: 60000 # 获取连接的最大等待时间(毫秒),超时则抛出异常
# -------------------- 从库:PostgreSQL(存储位置表、日志表等大流量数据) --------------------
slave:
type: com.alibaba.druid.pool.DruidDataSource
# PostgreSQL驱动类(固定写法,不可修改)
driver-class-name: org.postgresql.Driver
# 数据库连接地址(替换为自己的PostgreSQL地址、数据库名)
url: jdbc:postgresql://localhost:5432/position_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
# PostgreSQL账号(默认postgres,可根据实际修改)
username: postgres
# PostgreSQL密码(替换为自己的PostgreSQL密码)
password: 123456
# 位置库、日志库查询量大,连接数适当调大,避免查询阻塞
druid:
initial-size: 8
min-idle: 8
max-active: 40
max-wait: 60000
# ===================== 分片规则(核心:多表独立分表,分库路由) =====================
rules:
sharding:
# 全局分库策略(所有表通用,自动区分主库/从库)
# 原理:根据实体类中的dbType字段值,路由到对应的数据源
default-database-strategy:
standard:
sharding-column: db_type # 分库字段(实体类中必须有这个字段)
sharding-algorithm-name: database-inline # 分库算法名称(与下方算法定义对应)
# 多表分表规则配置(核心!每张表独立配置,互不干扰)
# 格式:tables: 表1: 规则1; 表2: 规则2; ...
tables:
# -------------------- 1. 位置表:position(按月分表) --------------------
position:
# 真实数据节点:slave(从库PG) + 分表后缀(202501~203012,覆盖5年)
# 格式:数据源.表名_${后缀范围},ShardingSphere自动识别所有分表
actual-data-nodes: slave.position_${202501..203012}
# 分表策略(按时间字段分表)
table-strategy:
standard:
sharding-column: create_time # 分表字段(必须是时间类型,如LocalDateTime)
sharding-algorithm-name: position-month # 分表算法名称(独立,不与其他表冲突)
# -------------------- 2. 日志表:log(按月分表,新增,与position独立) --------------------
log:
# 真实数据节点:与position同属slave库,表名格式log_yyyyMM
actual-data-nodes: slave.log_${202501..203012}
# 分表策略:与position一致(按月分表),也可自定义(如按天、按季度)
table-strategy:
standard:
sharding-column: create_time # 分表字段(日志创建时间)
sharding-algorithm-name: log-month # 日志表独立分表算法
# 分片算法定义(分库算法 + 多表分表算法,可共用也可独立)
sharding-algorithms:
# 1. 分库算法(全局共用,路由主库/从库)
database-inline:
type: INLINE # 内联算法(简单高效,适合固定规则路由)
props:
# 算法表达式:dbType字段值为slave → 路由到slave(PG),否则路由到master(MySQL)
algorithm-expression: "$->{db_type == 'slave' ? 'slave' : 'master'}"
# 2. 位置表分表算法(按月分表)
position-month:
type: MONTH # 按月分片算法(ShardingSphere内置,无需自定义)
props:
datetime-pattern: yyyy-MM-dd HH:mm:ss # 时间字段格式(与实体类createTime格式一致)
sharding-suffix-pattern: yyyyMM # 分表后缀格式(如202603,对应表名position_202603)
# 3. 日志表分表算法(按月分表,与position独立,可单独修改)
log-month:
type: MONTH
props:
datetime-pattern: yyyy-MM-dd HH:mm:ss
sharding-suffix-pattern: yyyyMM
# ===================== MyBatis-Plus 全局配置(生产通用) =====================
mybatis-plus:
mapper-locations: classpath:mapper/*.xml # Mapper.xml文件路径(固定写法,无需修改)
type-aliases-package: com.example.entity # 实体类别名包路径(替换为自己的实体类包)
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰(如数据库create_time → 实体类createTime)
call-setters-on-nulls: true # 开启空值调用setter方法(避免查询结果中空值字段不返回)
global-config:
db-config:
id-type: assign_id # 全局ID生成策略(雪花算法,分布式环境唯一,无需手动生成ID)
logic-delete-field: is_deleted # 逻辑删除字段名(可选,若不需要逻辑删除可删除这3行)
logic-delete-value: 1 # 逻辑删除值(1=已删除)
logic-not-delete-value: 0 # 逻辑未删除值(0=正常)
# ===================== Druid 监控配置(生产标配,可选但推荐) =====================
# 访问地址:http://localhost:8080/sharding-demo/druid(对应server.servlet.context-path)
druid:
stat-view-servlet:
enabled: true # 开启监控面板
url-pattern: /druid/* # 监控面板访问路径
login-username: admin # 监控面板登录账号
login-password: admin # 监控面板登录密码
allow: 127.0.0.1 # 允许访问的IP(本地调试,生产可配置具体IP)
web-stat-filter:
enabled: true # 开启Web监控(统计请求、SQL执行情况等)
url-pattern: /* # 监控所有请求
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除静态资源和监控自身路径
四、核心代码实现(零侵入业务,所有代码带详细注释)
所有代码均按规范包结构组织,无需修改,直接复制到对应包下即可,重点关注实体类的dbType字段(用于分库路由)。
4.1 包结构(规范,避免混乱)
TypeScript
com.example
├── ShardingMultiTableApplication.java # 启动类
├── config
│ └── MyBatisPlusConfig.java # MyBatis-Plus分页配置(双库适配)
├── entity
│ ├── Position.java # 位置表实体类(分表)
│ └── Log.java # 日志表实体类(分表)
├── mapper
│ ├── PositionMapper.java # 位置表Mapper(零代码)
│ └── LogMapper.java # 日志表Mapper(零代码)
├── service
│ ├── PositionService.java # 位置表业务层(无分表逻辑)
│ └── LogService.java # 日志表业务层(无分表逻辑)
└── schedule
└── TableCreateSchedule.java # 定时任务(自动创建分表)
4.2 启动类(核心,开启定时任务)
java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动类
* @EnableScheduling:开启定时任务(用于自动创建分表)
* @SpringBootApplication:SpringBoot核心注解,自动扫描组件
*/
@SpringBootApplication
@EnableScheduling
public class ShardingMultiTableApplication {
public static void main(String[] args) {
// 启动SpringBoot应用
SpringApplication.run(ShardingMultiTableApplication.class, args);
}
}
4.3 MyBatis-Plus分页配置(双库自动适配)
一个配置类,自动适配MySQL和PostgreSQL分页语法,无需区分数据库类型,分库分表、跨表分页均正常生效:
java
package com.example.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus 分页插件配置
* 核心:自动识别数据库类型(MySQL/PostgreSQL),无需手动指定
* 支持:主库MySQL分页、从库PostgreSQL分页、跨表分页
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件:DbType.AUTO 自动识别数据库类型,适配MySQL和PostgreSQL
// 无需修改,直接使用
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.AUTO));
return interceptor;
}
}
4.4 实体类(分库分表核心,dbType字段是关键)
实体类只需添加dbType字段(固定值slave),即可自动路由到PostgreSQL从库;不加该字段,自动路由到MySQL主库。
4.4.1 位置表实体类(Position)
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 位置表实体类(按月分表,存储在PostgreSQL从库)
* @TableName("position"):逻辑表名(开发时永远操作这个表名,ShardingSphere自动路由到真实分表)
* 真实分表名:position_202603、position_202604...(按月生成)
*/
@Data // Lombok注解,自动生成get/set/toString等方法
@TableName("position")
public class Position {
/**
* 主键ID(雪花算法,自动生成,无需手动赋值)
*/
private Long id;
/**
* 关联业务ID(如用户ID、设备ID,根据实际业务修改)
*/
private Long bizId;
/**
* 经度(精度10位,小数6位,满足位置信息精度要求)
*/
private Double longitude;
/**
* 纬度(精度10位,小数6位)
*/
private Double latitude;
/**
* 详细地址(如:北京市朝阳区XX街道)
*/
private String address;
/**
* 分表关键字段(必须是LocalDateTime类型,与yml中分表算法的时间格式一致)
* ShardingSphere根据该字段自动路由到对应的月表
*/
private LocalDateTime createTime;
/**
* 分库标记字段(核心!固定值为slave,自动路由到PostgreSQL从库)
* 若不添加该字段,默认路由到MySQL主库
*/
private String dbType = "slave";
}
4.4.2 日志表实体类(Log)
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 日志表实体类(按月分表,存储在PostgreSQL从库)
* 与Position表独立,分表规则互不干扰,写法完全一致
*/
@Data
@TableName("log") // 逻辑表名,真实分表名:log_202603、log_202604...
public class Log {
/**
* 主键ID(雪花算法自动生成)
*/
private Long id;
/**
* 日志内容(如:用户XXX执行了XXX操作)
*/
private String content;
/**
* 操作人ID(关联用户表,根据实际业务修改)
*/
private Long operatorId;
/**
* 分表关键字段(与yml中分表算法对应)
*/
private LocalDateTime createTime;
/**
* 分库标记字段(固定slave,路由到PostgreSQL从库)
*/
private String dbType = "slave";
}
4.5 Mapper层(零代码,直接继承BaseMapper)
无需写任何SQL,无需处理分表逻辑,MyBatis-Plus的BaseMapper提供所有CRUD方法,ShardingSphere自动处理分表路由。
4.5.1 PositionMapper
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Position;
import org.apache.ibatis.annotations.Mapper;
/**
* 位置表Mapper
* @Mapper:MyBatis注解,标识为Mapper接口,Spring自动扫描
* 继承BaseMapper<Position>:获得所有CRUD方法(selectList、insert、update等)
* 无需写任何额外代码,分表路由由ShardingSphere自动处理
*/
@Mapper
public interface PositionMapper extends BaseMapper<Position> {
}
4.5.2 LogMapper
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Log;
import org.apache.ibatis.annotations.Mapper;
/**
* 日志表Mapper
* 与PositionMapper写法完全一致,独立无关联
*/
@Mapper
public interface LogMapper extends BaseMapper<Log> {
}
4.6 Service层(业务代码零分表逻辑)
业务层完全像操作单表一样开发,无需关心分表、跨表、分库逻辑,ShardingSphere自动完成路由和结果合并。
4.6.1 PositionService
java
package com.example.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.Position;
import com.example.mapper.PositionMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
/**
* 位置表业务层
* 完全无分表逻辑,像单表一样开发,ShardingSphere自动处理分表路由
*/
@Service
public class PositionService {
// 注入Mapper(Spring自动注入,无需手动new)
@Resource
private PositionMapper positionMapper;
/**
* 1. 插入位置数据(自动路由到对应月表)
* 例:createTime为2026-03-15 → 自动插入position_202603表
*/
public void savePosition(Position position) {
// 无需处理分表,直接调用insert方法
positionMapper.insert(position);
}
/**
* 2. 单月查询(自动定位到当月分表)
* @param bizId 业务ID(如设备ID)
* @param year 年份(如2026)
* @param month 月份(如3)
* @return 当月所有位置数据
*/
public List<Position> getPositionBySingleMonth(Long bizId, int year, int month) {
LambdaQueryWrapper<Position> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Position::getBizId, bizId) // 业务条件过滤
// 时间范围:当月1号0点 ~ 当月最后一天23点59分
.between(Position::getCreateTime,
LocalDateTime.of(year, month, 1, 0, 0),
LocalDateTime.of(year, month, LocalDateTime.now().getMonth().length(true), 23, 59));
// 自动路由到对应月表(如position_202603),无需手动指定表名
return positionMapper.selectList(wrapper);
}
/**
* 3. 跨月查询(自动合并多表结果)
* 例:查询2026-03-15 ~ 2026-04-15 → 自动查询position_202603 + position_202604,合并结果
*/
public List<Position> getPositionByCrossMonth(Long bizId, LocalDateTime start, LocalDateTime end) {
LambdaQueryWrapper<Position> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Position::getBizId, bizId)
.between(Position::getCreateTime, start, end);
// ShardingSphere自动并行查询多张分表,合并结果返回,业务层无感知
return positionMapper.selectList(wrapper);
}
/**
* 4. 分页查询(跨表分页自动处理)
* 支持PostgreSQL分页,自动计算总条数、总页数
*/
public Page<Position> pagePosition(Page<Position> page, Long bizId) {
LambdaQueryWrapper<Position> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Position::getBizId, bizId)
.orderByDesc(Position::getCreateTime); // 按创建时间降序
// 自动处理跨表分页,无需额外配置
return positionMapper.selectPage(page, wrapper);
}
}
4.6.2 LogService
java
package com.example.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.Log;
import com.example.mapper.LogMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
/**
* 日志表业务层
* 与PositionService写法完全一致,独立处理日志业务,分表逻辑自动生效
*/
@Service
public class LogService {
@Resource
private LogMapper logMapper;
/**
* 插入日志(自动路由到对应月表)
*/
public void saveLog(Log log) {
logMapper.insert(log);
}
/**
* 跨月查询日志
*/
public List<Log> getLogByCrossMonth(Long operatorId, LocalDateTime start, LocalDateTime end) {
LambdaQueryWrapper<Log> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Log::getOperatorId, operatorId)
.between(Log::getCreateTime, start, end);
return logMapper.selectList(wrapper);
}
/**
* 日志分页查询(PostgreSQL分页自动适配)
*/
public Page<Log> pageLog(Page<Log> page) {
LambdaQueryWrapper<Log> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(Log::getCreateTime);
return logMapper.selectPage(page, wrapper);
}
}
4.7 定时任务(每月自动创建分表,无需人工干预)
每月最后一天23:50自动创建下个月的Position和Log分表(PostgreSQL语法),自动创建索引,避免分表缺失导致插入失败。
java
package com.example.schedule;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 定时任务:每月自动创建PostgreSQL分表(Position + Log)
* 执行时间:每月最后一天 23:50:00
* 功能:自动创建下个月的分表 + 索引,确保数据插入正常
*/
@Component // 标识为Spring组件,被自动扫描
public class TableCreateSchedule {
// 注入JdbcTemplate,自动使用ShardingSphere管理的数据源(这里自动关联PostgreSQL从库)
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 定时规则:cron表达式 "0 50 23 L * ?"
* 0:秒,50:分,23:时,L:当月最后一天,*:任意月份,?:任意星期(避免与L冲突)
*/
@Scheduled(cron = "0 50 23 L * ?")
public void createNextMonthTables() {
// 1. 获取下个月的日期格式(yyyyMM,如202604)
LocalDate nextMonth = LocalDate.now().plusMonths(1);
String tableSuffix = nextMonth.format(DateTimeFormatter.ofPattern("yyyyMM"));
// 2. 自动创建位置表分表(position_yyyyMM)
createPositionTable(tableSuffix);
// 3. 自动创建日志表分表(log_yyyyMM)
createLogTable(tableSuffix);
System.out.println("✅ 下个月分表创建完成!后缀:" + tableSuffix);
}
/**
* 创建位置表分表(PostgreSQL语法,与实体类字段完全一致)
*/
private void createPositionTable(String suffix) {
String tableName = "position_" + suffix;
// 建表SQL(PostgreSQL语法,字段类型与实体类对应)
String createSql = "CREATE TABLE IF NOT EXISTS " + tableName + " (\n" +
" id BIGINT PRIMARY KEY,\n" + // 主键ID(雪花算法)
" biz_id BIGINT,\n" + // 业务ID
" longitude NUMERIC(10,6),\n" + // 经度(PostgreSQL用NUMERIC对应Java Double)
" latitude NUMERIC(10,6),\n" + // 纬度
" address VARCHAR(255),\n" + // 地址
" create_time TIMESTAMP,\n" + // 分表时间字段
" db_type VARCHAR(20) DEFAULT 'slave'\n" + // 分库标记,默认slave
")";
// 创建索引(必须!加速分表查询,根据分表字段和业务查询字段建索引)
String idxCreateTime = "CREATE INDEX IF NOT EXISTS idx_" + suffix + "_position_create_time ON " + tableName + "(create_time)";
String idxBizId = "CREATE INDEX IF NOT EXISTS idx_" + suffix + "_position_biz_id ON " + tableName + "(biz_id)";
// 执行建表和建索引(JdbcTemplate自动使用PostgreSQL数据源)
jdbcTemplate.execute(createSql);
jdbcTemplate.execute(idxCreateTime);
jdbcTemplate.execute(idxBizId);
System.out.println("✅ 位置表分表创建成功:" + tableName);
}
/**
* 创建日志表分表(PostgreSQL语法,与实体类字段完全一致)
*/
private void createLogTable(String suffix) {
String tableName = "log_" + suffix;
String createSql = "CREATE TABLE IF NOT EXISTS " + tableName + " (\n" +
" id BIGINT PRIMARY KEY,\n" +
" content VARCHAR(500),\n" + // 日志内容,长度可根据实际调整
" operator_id BIGINT,\n" + // 操作人ID
" create_time TIMESTAMP,\n" +
" db_type VARCHAR(20) DEFAULT 'slave'\n" +
")";
// 创建索引(加速查询)
String idxCreateTime = "CREATE INDEX IF NOT EXISTS idx_" + suffix + "_log_create_time ON " + tableName + "(create_time)";
String idxOperatorId = "CREATE INDEX IF NOT EXISTS idx_" + suffix + "_log_operator_id ON " + tableName + "(operator_id)";
jdbcTemplate.execute(createSql);
jdbcTemplate.execute(idxCreateTime);
jdbcTemplate.execute(idxOperatorId);
System.out.println("✅ 日志表分表创建成功:" + tableName);
}
}
五、关键配置&代码讲解(新手必看,避坑指南)
5.1 为什么要关闭Spring默认数据源自动配置?
核心原因:ShardingSphere会全权接管所有数据源(master+slave),如果不关闭Spring默认的DataSourceAutoConfiguration,Spring会自动加载单数据源,与ShardingSphere的多数据源冲突,导致项目启动失败。这是分库分表的必配项,缺一不可。
5.2 分库路由原理(自动区分MySQL/PostgreSQL)
通过实体类中的dbType字段实现自动路由:
-
实体类加
private String dbType = "slave";→ 自动路由到PostgreSQL从库(位置表、日志表) -
实体类不加dbType字段 → 自动路由到MySQL主库(业务表,如User、Order)
-
无需手动编写路由逻辑,ShardingSphere根据yml中的分库算法自动处理,零代码侵入。
5.3 多表分表扩展(新增分表只需3步)
如果后续需要新增分表(如轨迹表track),只需3步,无需修改原有代码:
-
在yml的
spring.shardingsphere.rules.sharding.tables下新增track表的分表规则(复制position的配置,修改表名和算法名); -
在
sharding-algorithms下新增track表的分表算法(复制position-month,修改算法名); -
编写Track实体类,添加dbType="slave"字段,编写对应的Mapper和Service(与Position完全一致)。
5.4 生产环境优化建议(必看)
-
关闭SQL打印:生产环境将
spring.shardingsphere.props.sql-show改为false,避免日志冗余; -
调整连接数:根据业务流量调整Druid的initial-size、max-active,避免连接不足或浪费;
-
索引优化:所有分表必须创建分表字段(create_time)和业务查询字段(如bizId)的索引,否则查询缓慢;
-
数据归档:历史分表(如1年前的表)可归档到冷存储,减少当前库的数据量,提升查询性能;
-
避免全表扫描:查询时必须携带create_time时间范围,否则ShardingSphere会扫描所有分表,导致性能暴跌。
六、测试验证(快速确认是否生效)
启动项目后,通过以下方式验证分库分表是否生效:
-
插入测试:调用PositionService.savePosition(),传入createTime为2026-03-15,查看PostgreSQL是否自动创建position_202603表,并插入数据;
-
跨月查询:调用getPositionByCrossMonth(),查询2026-03-25 ~ 2026-04-05,查看控制台SQL打印,是否同时查询position_202603和position_202604;
-
分页测试:调用pagePosition(),传入Page(1,10),查看返回结果是否正确,总条数是否准确;
-
自动建表:可临时修改cron表达式为当前时间(如"0 0 12 * * ?"),启动项目,查看是否自动创建下个月的分表。
七、常见问题排查(避坑必备)
| 问题现象 | 排查方向 |
|---|---|
| 项目启动失败,提示数据源冲突 | 检查是否关闭了Spring默认数据源自动配置(spring.autoconfigure.exclude) |
| 插入数据时报表不存在 | 1. 检查定时任务是否开启(@EnableScheduling);2. 手动创建对应分表;3. 检查分表后缀格式是否正确(yyyyMM) |
| 查询不到数据,但数据已插入 | 1. 检查createTime字段格式是否与yml中datetime-pattern一致;2. 检查dbType字段是否为slave;3. 查看控制台SQL,确认路由的表名是否正确 |
| PostgreSQL分页查询异常 | 检查MyBatis-Plus分页配置是否为DbType.AUTO,不要手动指定为MySQL |
八、总结
本文基于JDK17+Druid1.2.27+SpringBoot3+ShardingSphere5,实现了「主库MySQL+从库PostgreSQL」的多表分库分表方案,核心优势的是:
-
配置极简规范:ShardingSphere独立配置,可读性强,便于维护和扩展;
-
零侵入业务:业务层完全像单表一样开发,分库分表逻辑由框架自动处理;
-
多表支持:Position、Log等多张表可独立分表,规则互不干扰;
-
自动适配:双库分页、自动建表,无需人工干预,可直接上生产;
-
性能优异:ShardingSphere客户端直连数据库,无中间件损耗,小表+索引实现毫秒级查询。
所有代码和配置均已测试通过,可直接复制替换数据库信息后使用,适合互联网公司位置数据、日志数据等大流量场景的分库分表需求,新手也能快速上手。如果有其他分表场景(如按天、按季度分表),只需修改yml中的分表算法即可。
最后,觉得有用的话,记得点赞收藏,关注我,持续分享Java后端实战干货!