4、ShardingSphere-JDBC 实战:Maven 依赖配置、数据源与分片规则配置(YAML/Java API)、标准分片算法与复合分片算法、强制路由(Hint)的使用
好,咱们直接进入正题。
上一章我们把分库分表的理论讲透了。这一章,咱们来点实在的——上手写代码。
我会带着你,从零搭建一个 ShardingSphere-JDBC 的工程。你跟着我走一遍,基本就能应付大部分的分片场景了。
4.1 Maven 依赖配置:别踩版本坑
先说依赖。ShardingSphere-JDBC 的版本迭代挺快的,我个人习惯用 5.1.2 这个版本,比较稳定,社区反馈也好。
你直接在你的 pom.xml 里加上这一段:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.2</version>
</dependency>
另外,别忘了加上数据库驱动。我用 MySQL 8.0 举例:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
嗯,依赖就这么多。简单吧?
4.2 数据源与分片规则配置:YAML 还是 Java API?
ShardingSphere-JDBC 支持两种配置方式:YAML 和 Java API。
我个人建议:小项目用 YAML,大项目用 Java API。为什么?YAML 改起来快,但不好做动态调整;Java API 虽然代码多,但灵活,适合做配置中心动态下发。
4.2.1 YAML 配置方式
在 application.yml 里,你只需要定义数据源和分片规则。我给你一个最简的示例:
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/order_db0
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/order_db1
username: root
password: 123456
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-inline
sharding-algorithms:
table-inline:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 2}
db-inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 2}
你看,这个配置做了两件事:
- 按
user_id分库,偶数进 ds0,奇数进 ds1。 - 按
order_id分表,偶数进 t_order_0,奇数进 t_order_1。
4.2.2 Java API 配置方式
如果你喜欢用代码控制一切,Java API 更适合你。下面是一个等价的配置:
@Configuration
public class ShardingConfig {
@Bean
public ShardingSphereDataSource shardingSphereDataSource() {
// 1. 配置数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("ds0", createDataSource("jdbc:mysql://localhost:3306/order_db0"));
dataSourceMap.put("ds1", createDataSource("jdbc:mysql://localhost:3306/order_db1"));
// 2. 配置分片规则
ShardingRuleConfiguration ruleConfig = new ShardingRuleConfiguration();
ruleConfig.getTables().add(getOrderTableRule());
// 3. 构建数据源
return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(ruleConfig), new Properties());
}
private DataSource createDataSource(String url) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
private ShardingTableRuleConfiguration getOrderTableRule() {
ShardingTableRuleConfiguration tableRule = new ShardingTableRuleConfiguration("t_order", "ds$->{0..1}.t_order_$->{0..1}");
tableRule.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("user_id", "db-inline"));
tableRule.setTableShardingStrategy(new StandardShardingStrategyConfiguration("order_id", "table-inline"));
return tableRule;
}
}
说白了,YAML 和 Java API 本质是一样的,只是表达方式不同。你选哪个都行,看团队习惯。
4.3 标准分片算法与复合分片算法
分片算法是分库分表的核心。ShardingSphere 提供了好几种,我重点讲两个最常用的:标准分片算法 和 复合分片算法。
4.3.1 标准分片算法
标准分片算法,说白了就是单字段分片。你只需要一个字段来决定数据去哪张表。
比如上面的 INLINE 算法,就是标准分片的一种。它支持 Groovy 表达式,简单粗暴。
但如果你需要更复杂的逻辑,比如按时间范围分片,可以自定义:
public class MyStandardShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
// 根据 order_id 精确分片
long id = shardingValue.getValue();
String target = "t_order_" + (id % 2);
if (!availableTargetNames.contains(target)) {
throw new IllegalArgumentException("目标表不存在: " + target);
}
return target;
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
// 范围查询时,返回所有可能的目标表
return availableTargetNames;
}
}
嗯,这里要注意:范围查询时,如果你不重写 doSharding 的范围方法,默认会全表扫描。我曾经因为这个踩过坑,查询性能直接崩了。
4.3.2 复合分片算法
复合分片算法,就是多字段分片。比如你想同时根据 user_id 和 order_date 来决定数据去哪张表。
为什么需要复合分片?你想想看,如果只按 user_id 分,那同一个用户的所有订单都在一张表里,数据量大了还是扛不住。加上时间维度,就能把数据打得更散。
实现起来也不复杂:
public class MyComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValue) {
// 获取分片键的值
Map<String, Collection<Long>> columnValues = shardingValue.getColumnNameAndShardingValuesMap();
Collection<Long> userIds = columnValues.get("user_id");
Collection<Long> orderDates = columnValues.get("order_date");
// 自定义分片逻辑:比如 user_id % 2 + order_date 的月份
Set<String> result = new HashSet<>();
for (Long userId : userIds) {
for (Long orderDate : orderDates) {
int suffix = (int) (userId % 2) * 10 + (int) (orderDate % 12);
result.add("t_order_" + suffix);
}
}
return result;
}
}
4.4 强制路由(Hint):绕过分片键的利器
最后,咱们聊聊强制路由。
什么叫强制路由?说白了,就是不按分片键,直接指定数据去哪张表。
什么时候用?我举个例子:
- 你有一个报表查询,需要扫描所有分片。
- 或者,你明确知道某条数据在 ds1.t_order_0 里,不想让框架去计算。
这时候,Hint 就派上用场了。
使用 Hint 分两步:
4.4.1 配置 Hint 分片算法
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
hint-test:
type: HINT_INLINE
props:
algorithm-expression: ds$->{hintValue % 2}
4.4.2 在代码中注入 Hint
// 强制路由到 ds0
HintManager hintManager = HintManager.getInstance();
hintManager.addDatabaseShardingValue("t_order", 0);
// 执行查询
List<Order> orders = orderMapper.selectList(null);
// 用完一定要关闭
hintManager.close();
强制路由虽然好用,但别滥用。它绕过了分片键,本质上是在破坏分片规则。只在特殊场景下用,比如数据迁移、全表扫描。
小结
这一章,我们走通了 ShardingSphere-JDBC 的完整配置流程。从 Maven 依赖,到 YAML 和 Java API 两种配置方式,再到标准分片算法和复合分片算法的实现,最后聊了强制路由的用法。
你把这些代码跑一遍,基本就能掌握 ShardingSphere-JDBC 的核心用法了。下一章,咱们聊聊分布式事务——那才是真正的硬骨头。
好了,今天就到这里。有问题随时交流。