jdbc层分库分表路由本质是拦截sql执行前的连接获取过程,根据分片键动态选择目标库和表;需自定义shardrouter计算dbindex与tablesuffix,通过abstractroutingdatasource路由数据源,并代理preparedstatement重写sql表名,同时确保单事务仅操作同库且主键全局唯一。

在 JDBC 层实现分库分表路由,本质是**拦截 SQL 执行前的连接获取过程,根据分片键(如 user_id、order_no)动态选择目标数据库和表名**。Java 本身不提供原生支持,需结合 DataSource 封装或代理(如自定义 Wrapper、JDBC Driver 代理、或轻量框架)来完成。下面给出可落地的轻量级实现思路,不依赖 ShardingSphere 等重型框架。
1. 定义分片规则与路由策略
先明确你的分片维度(如按 user_id 模 4 分库 + 按 order_id 模 8 分表),并封装成可复用的路由类:
- 用一个 ShardRouter 类统一计算:输入分片键 → 输出
dbIndex和tableSuffix - 支持多租户、日期分表(如
order_202409)、一致性哈希等常见策略,但初期建议从取模开始 - 避免在 SQL 中硬编码表名,所有表名通过
OrderTable.getActualName(userId, orderId)动态生成
2. 封装逻辑 DataSource(推荐:AbstractRoutingDataSource 变体)
Spring 用户可直接继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey():
- 将分片键(如 user_id)存入
ThreadLocal<shardcontext></shardcontext>,在业务入口(如 Service 方法)中设置 - 路由方法中读取 ThreadLocal,调用
ShardRouter.routeDb(user_id)返回数据源 key(如"ds_0"、"ds_3") - 配置多个底层真实 DataSource(如 HikariCP 实例),由 key 映射到具体连接池
3. 表名动态替换(SQL 层适配)
JDBC 不自动改表名,所以需在执行前处理 SQL:
- 对
PreparedStatement或Statement的 SQL 字符串做简单模板替换,例如把"INSERT INTO order" → "INSERT INTO order_003" - 更稳妥方式:用 PreparedStatementWrapper 代理,在
execute/executeUpdate前解析并重写 SQL(可用 JSqlParser 库辅助识别表名) - 注意 WHERE 条件中的分片键必须存在,否则无法路由 —— 这是强约束,需在业务层保障
4. 注意事务与连接一致性
跨库操作天然不支持分布式事务(XA 成本高),因此要规避:
- 单次请求只操作同一个库内的一个或多个表(如用户中心场景下,user + user_profile 必须同库)
- 不要在一个事务里更新
ds_0.order_1和ds_1.order_5—— 路由会失败或产生不可预知行为 - 使用
@Transactional时,确保其作用域内只涉及单数据源;必要时手动控制 Connection 生命周期
不复杂但容易忽略:分库后主键不能依赖数据库自增,需改用雪花算法、UUID 或号段模式生成全局唯一 ID。否则跨库插入会冲突。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











