在微服务或高并发系统中,分库分表已成为应对海量数据的标配方案。而 Apache ShardingSphere 作为国内主流的分布式数据库中间件,提供了强大的透明化分片能力。与此同时,很多项目也使用了 @DS 注解(如 MyBatis-Plus 的 dynamic-datasource)来实现多数据源切换。

但问题来了:ShardingSphere 和 @DS 能否一起用?怎么用?

本文将深入剖析 ShardingSphere 的工作原理,并给出 ShardingSphere 与 @DS 完美整合的实战方案


一、核心矛盾:两个“数据源管理层”的冲突

  • @DS(如 dynamic-datasource):在应用层通过 ThreadLocal 切换 顶层数据源(DataSource),适用于普通数据库连接。
  • ShardingSphere:它本身就是一个 逻辑数据源(ShardingSphereDataSource,内部封装了多个真实的物理数据源,并负责 SQL 解析、路由、改写、执行与结果归并。

❗关键点:ShardingSphere 不识别 @DS@DS不知道 ShardingSphere 内部结构

因此,直接混用会导致:

  • 分片失效
  • 数据源切换混乱
  • 连接池冲突

那怎么办?

✅ 正确思路是:把 ShardingSphere 当作一个“黑盒数据源”,注册到动态数据源框架中,由 @DS 来切换它。


二、ShardingSphere 如何知道有哪些库和表?

很多人误以为 ShardingSphereDriver 会自动发现库表,其实完全不是!

真相:一切靠配置!

ShardingSphere 的分片规则、数据源列表、逻辑表与实际节点的映射关系,全部来自 显式配置,常见方式有:

  1. YAML 文件(最常用)
  2. Spring Boot Properties
  3. 编程式 API 构建

示例:YAML 配置片段

# shardingsphere-config.yaml
dataSources:
  ds0:
    url: jdbc:mysql://localhost:3306/order_db_0
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  ds1:
    url: jdbc:mysql://localhost:3306/order_db_1
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

rules:
- !SHARDING
  tables:
    t_order:
      actualDataNodes: ds${0..1}.t_order_${0..1}
      databaseStrategy:
        standard:
          shardingColumn: user_id
          shardingAlgorithmName: db_inline
      tableStrategy:
        standard:
          shardingColumn: order_id
          shardingAlgorithmName: table_inline
  shardingAlgorithms:
    db_inline:
      type: INLINE
      props:
        algorithm-expression: ds${user_id % 2}
    table_inline:
      type: INLINE
      props:
        algorithm-expression: t_order_${order_id % 2}

在这个配置中,ShardingSphere 明确知道:

  • 有两个物理数据源:ds0ds1
  • 逻辑表 t_order 对应 4 个真实表
  • 分库依据 user_id,分表依据 order_id

🔍 重点:这些信息 不是由 ShardingSphereDriver 自动获取的,而是你写死在配置里的!

ShardingSphereDriver 只是一个 JDBC 驱动类,用于兼容 jdbc:shardingsphere:... 格式的 URL,在 Spring Boot + YAML 模式下通常 不需要显式使用它


三、关键澄清:什么是“物理数据源”?

这是最容易混淆的地方!

ShardingSphere 中所说的“多个物理数据源”,指的就是你在配置中定义的那些真实的、可直接连接数据库的 DataSource 实例(如 HikariCP、Druid 等)——它们确实是标准意义上的“顶层数据源”(Top-level DataSource)。

但在 ShardingSphere 的上下文中,它们被封装在 ShardingSphereDataSource 内部,对外不可见。

举个例子:

当你配置了 ds0ds1,ShardingSphere 在启动时会:

  1. ds0 创建一个 HikariDataSource 实例;
  2. ds1 创建另一个 HikariDataSource 实例;
  3. 将这两个 物理数据源 保存在内部路由引擎中;
  4. 对外只暴露一个 逻辑数据源 ShardingSphereDataSource

当你执行 SQL 时:

  • 应用从 ShardingSphereDataSource.getConnection() 获取连接;
  • ShardingSphere 解析 SQL,根据分片规则决定目标(比如 ds1);
  • 内部调用 ds1.getConnection(),在真实数据库上执行;
  • 结果返回给应用。

整个过程对开发者透明。


四、整合难点:为什么不能直接在 YAML 里配 ShardingSphere?

你可能会想:既然 dynamic-datasource 支持多数据源,那我直接在 application.yml 里加一个 shardingDs 不就行了?

shardingDs:
  url: jdbc:mysql://...   # ❌ 问题来了:ShardingSphere 没有单一 URL!

ShardingSphere 不是由一个 URL 定义的!它需要:

  • 多个物理库地址
  • 分片规则
  • 表映射关系

这些无法通过 standard 的 url/username/password 表达。

所以,我们必须手动构建 ShardingSphereDataSource,并通过 Spring Bean 注入。


五、核心技巧:“fake 配置”机制详解

这是整合成功的关键!我们来彻底讲清楚这句常被误解的话:

“fake 配置,实际会被 Spring 容器中的同名 Bean 替换”

🤔 什么是 fake 配置?

dynamic-datasource-spring-boot-starter 在启动时,会扫描 spring.datasource.dynamic.datasource 下的所有 key(如 master, slave1),并尝试为每个 key 创建 DataSource

但它有一个重要设计原则

如果 Spring 容器中已经存在一个同名的 DataSource Bean,则优先使用该 Bean,忽略 YAML 中的配置!

这就给了我们一个“后门”:
我们可以先用 Java 代码创建好 ShardingSphere 数据源,再在 YAML 中写一个“空壳”配置仅用于注册名字

✅ 正确做法分两步:

第一步:Java Config 中定义真实数据源
@Configuration
public class ShardingSphereConfig {

    @Bean("shardingDs") // 名字必须匹配
    public DataSource shardingDataSource() throws SQLException {
        Resource resource = new ClassPathResource("shardingsphere-config.yaml");
        return YamlShardingSphereDataSourceFactory.createDataSource(resource.getFile());
    }
}
第二步:YAML 中写“fake 配置”占位
spring:
  datasource:
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/app_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        shardingDs:
          # ⚠️ 注意:这里没有 url/username/password!
          # 这只是一个“名字声明”,告诉 dynamic-datasource:“存在一个叫 shardingDs 的数据源”
          # 实际对象来自上面的 @Bean("shardingDs")
          driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver

💡 为什么还要写 driver-class-name
因为 dynamic-datasource 要求每个数据源至少有一个驱动类,否则会报校验错误。
这里随便写一个(如 ShardingSphereDriver)即可,不会真正使用它

🔍 类比理解

想象公司 HR 系统:

  • 正常员工:填入职表(YAML),HR 自动生成档案。
  • 外部专家:你直接把他的名片(@Bean)交给 HR。
  • 但为了让他在通讯录里出现,你仍需在系统里“新建一条记录”,只写名字。
  • HR 一看:“哦,这个人已经有实体名片了”,就直接用名片,忽略表格内容。

这里的“新建记录”就是 fake 配置,“名片”就是你的 @Bean

❌ 如果不写 fake 配置?

即使你有 @Bean("shardingDs"),dynamic-datasource 根本不知道这个数据源要纳入管理

结果:使用 @DS("shardingDs") 时抛出异常:

com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: 
Cannot find dataSource of name [shardingDs]

六、整合方案:完整代码示例

1. 创建 ShardingSphere 配置类

@Configuration
public class ShardingSphereConfig {

    @Bean("shardingDs")
    public DataSource shardingDataSource() throws SQLException {
        Resource res = new ClassPathResource("shardingsphere-config.yaml");
        return YamlShardingSphereDataSourceFactory.createDataSource(res.getFile());
    }
}

2. application.yml 配置

spring:
  datasource:
    dynamic:
      primary: master
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/app_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        shardingDs:
          driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver  # fake

3. Service 中使用 @DS

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void saveLog(String log) {
        logMapper.insert(log); // 默认走 master
    }

    @DS("shardingDs")
    public List<Order> queryByUserId(Long userId) {
        return orderMapper.selectByUserId(userId); // 自动分库分表
    }
}

七、数据源层级结构图(重点!)

┌──────────────────────────────────────┐
│   Dynamic Datasource(@DS 切换层)    │
├───────────────────┬──────────────────┤
│   master          │   shardingDs     │ ←─ @DS 切换的目标(逻辑数据源)
│ (普通 DataSource) │ (ShardingSphere) │
└─────────┬─────────┴─────────┬────────┘
          │                   │
          │                   ▼
          │        ┌──────────────────────┐
          │        │ ShardingSphere 内部  │
          │        ├───────────┬──────────┤
          │        │ ds0       │ ds1      │ ←─ 物理数据源(真实 Hikari/Druid)
          │        │ (db_0)    │ (db_1)   │
          │        └───────────┴──────────┘
          │
          ▼
   app_db(普通库)

关键结论

  • @DS 只能切换到 mastershardingDs 这一层;
  • shardingDs 内部的 ds0/ds1 路由由 ShardingSphere 自动完成;
  • 物理数据源确实是标准 DataSource,但属于 ShardingSphere 的内部实现

八、常见误区与注意事项

误区正确认知
“ShardingSphere 会自动扫描所有库表”❌ 必须显式配置 actualDataNodes
“要用 ShardingSphereDriver 才能分片”❌ 在 YAML 模式下,物理数据源仍用 MySQL/PG 驱动
“@DS 可以在 ShardingSphere 内部切换子库”@DS 只能切换顶层数据源,不能干预内部路由
“fake 配置是用来连数据库的”❌ 它只是名字占位符,真实数据源来自 @Bean

九、总结

组件职责
@DS多个顶层数据源之间切换(如普通 DB 与 ShardingSphere 逻辑数据源)
ShardingSphere其内部封装的物理数据源之间 自动完成分库分表、SQL 路由、结果归并

最佳实践

将 ShardingSphere 视为一个“功能增强型数据源”,通过 @Bean 注册到 Spring 容器,并在 dynamic-datasource 中用 fake 配置占位。用 @DS("shardingDs") 切入分片世界,其余交给 ShardingSphere。

这样,你既能享受动态数据源的灵活性,又能利用 ShardingSphere 的强大分片能力,二者各司其职,完美协同。


十、扩展建议

  • 若业务复杂,可考虑使用 ShardingSphere-Proxy(独立部署),应用直连 Proxy,彻底解耦分片逻辑。
  • 分片键设计至关重要,避免跨库查询、全表扫描。
  • 结合 Seata 可实现分布式事务(需额外配置)。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐