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>
注意: 如果你用的是 Spring Boot 2.x,这个版本没问题。但如果你用的是 Spring Boot 3.x,记得把版本升到 5.3.x 以上,否则启动会报错。我曾经在这个坑里浪费了半天,最后发现是版本不兼容。

另外,别忘了加上数据库驱动。我用 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 支持两种配置方式:YAMLJava 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。
小技巧: 实际项目中,我建议分库键和分表键不要用同一个字段。否则数据分布会不均匀。比如 user_id 和 order_id 分开,能打散热点。

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_idorder_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;
    }
}
核心要点: 复合分片算法返回的是一个集合,ShardingSphere 会帮你做路由。但要注意,返回的表越多,查询性能越差。尽量让分片结果收敛到 2-4 张表。

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();
警告: HintManager 是线程绑定的。用完必须 close(),否则会造成内存泄漏。我建议用 try-with-resources 语法,自动释放。

强制路由虽然好用,但别滥用。它绕过了分片键,本质上是在破坏分片规则。只在特殊场景下用,比如数据迁移、全表扫描。

小结

这一章,我们走通了 ShardingSphere-JDBC 的完整配置流程。从 Maven 依赖,到 YAML 和 Java API 两种配置方式,再到标准分片算法和复合分片算法的实现,最后聊了强制路由的用法。

你把这些代码跑一遍,基本就能掌握 ShardingSphere-JDBC 的核心用法了。下一章,咱们聊聊分布式事务——那才是真正的硬骨头。

课后练习: 试着用复合分片算法,实现一个按「用户ID + 订单年份」分片的逻辑。然后写一个 Hint 路由,强制查询某张特定表的数据。

好了,今天就到这里。有问题随时交流。