news 2026/9/3 2:02:42

数据源备注与多数据源实践:让智能问数更准确的Spring Boot方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据源备注与多数据源实践:让智能问数更准确的Spring Boot方案
  • 背景:SQLBot 与智能问数为什么依赖“备注”

1.1 SQLBot 到底解决什么问题

SQLBot,简单理解就是一个“用自然语言查询数据库”的智能助手。用户不需要写 SQL,只需要用业务语言描述需求,例如“查一下最近 7 天每个支付渠道的订单金额”,SQLBot 负责完成从自然语言到 SQL 的转换,并执行查询、返回结果。

这个能力在传统 BI 时代是难以想象的。过去做一个报表,需要产品经理提需求、开发人员写 SQL、再配置图表,往往一两天才能交付一个分析指标。而 SQLBot 走的是“语义解析 + 动态生成 SQL”的路线,新问题进来就生成新查询,边际成本非常低。

但这也带来了一个容易被忽视的问题:SQLBot 对数据库元数据的依赖,远超传统 BI。传统 BI 的指标和维度是人工建模确定的,模型只需要在已有的指标池里做选择;而 SQLBot 是“即问即答”,它必须自己理解数据库里每个表、每个字段的业务含义。如果表结构本身没有注释、字段命名随意,那么模型再强大,也只能靠猜。猜对了是运气,猜错了就是线上事故。

所以,数据源导入备注并不是一个“锦上添花”的功能,它直接决定了智能问数的准确率和可用性。备注的本质,是把人工积累的业务经验显式化,变成机器可以消费的上下文。

1.2 数据源备注的三个层级

数据源备注可以从三个粒度来梳理:

第一层是数据源级备注。描述这个数据库整体承载什么业务。比如“订单交易主库,包含订单、支付、退款、售后等核心交易数据”或“商品中心库,包含商品、类目、品牌、库存数据”。这一层备注帮助 SQLBot 判断“用户问的问题该去哪个库查”。

第二层是表级备注。描述表的业务含义,解决“这个问题对应哪张表”的映射问题。比如order_info表备注为“订单主表,一个订单一条记录,包含订单基本信息”,order_pay表备注为“订单支付流水表,一个订单可能有多条付款记录”。表级备注写清楚主键粒度和核心职责,模型才能避免选错表。

第三层是字段级备注。描述字段的业务含义和枚举取值。比如status字段备注为“订单状态:1待支付、2已支付、3已发货、4已完成、5已取消”,pay_amount备注为“实付金额,单位元,保留两位小数”。字段级备注是所有错误 SQL 里最需要解决的问题,SQLBot 生成 WHERE 条件、GROUP BY 分组维度时,依赖的就是字段语义。

三个层级的备注并非孤立存在,它们在 SQLBot 的语义解析链路中是逐层落地的。数据源级备注决定数据范围,表级备注决定主表选择,字段级备注决定条件和聚合粒度。任何一个层级缺失,都会让模型退回到“字面猜测”模式。

1.3 备注如何参与 SQL 生成

我们可以用一个例子直观感受备注的影响。

假设用户提问:“统计每个省份的已完成订单数”。

如果没有备注,模型看到的表结构可能是:

CREATE TABLE `order_info` ( `id` bigint NOT NULL, `province` varchar(50) DEFAULT NULL, `status` tinyint DEFAULT NULL, `amount` decimal(10,2) DEFAULT NULL );

模型只能猜测province是省份,status是状态,但“已完成”对应哪个数字,无从知晓。最终生成的 SQL 可能是:

SELECT province, COUNT(*) FROM order_info WHERE status = 1 GROUP BY province;

如果 1 恰好是“待支付”,这个结果就是完全错误的。

如果我们在导入数据源时维护好了字段备注:

CREATE TABLE `order_info` ( `id` bigint NOT NULL COMMENT '订单主键', `province` varchar(50) DEFAULT NULL COMMENT '收货省份', `status` tinyint DEFAULT NULL COMMENT '订单状态:1待支付、2已支付、3已发货、4已完成、5已取消', `amount` decimal(10,2) DEFAULT NULL COMMENT '实付金额(元)' ) COMMENT='订单主表,一张订单一条记录';

SQLBot 就能准确解析出“已完成”对应status = 4,并按province分组。准确率提升是立竿见影的。后面章节,我们就从工程视角来落地这套“带备注的数据源导入方案”。

2. 环境准备与版本说明

2.1 运行环境

本文以 Java 后端技术栈为例,既然场景涉及 Spring Boot、MyBatis Plus、多数据源和 ShardingSphere,我们先统一环境范围。

  • 操作系统:Windows / macOS / Linux 均可,本文不依赖特定系统命令。
  • JDK:示例默认 JDK 8 或 JDK 11,Spring Boot 2.x 场景下足够。
  • 构建工具:Maven 3.6+。
  • 数据库:MySQL 5.7 或 8.0,实际生产可按需替换为 PostgreSQL、Oracle 等。
  • 框架版本:Spring Boot 2.x、MyBatis Plus 3.5.x、dynamic-datasource 3.x、ShardingSphere 5.x。

版本说明这里强调一句:Spring Boot 3.x 与 2.x 在包名和部分自动装配上存在差异,dynamic-datasource 与 ShardingSphere 不同大版本的 API 也有调整。本文示例以业界最常见的 Spring Boot 2.x 组合为准,如果项目使用的是 Spring Boot 3.x,请把javax.annotation替换为jakarta.annotation,并参考官方文档升级对应 starter 版本。

2.2 Maven 依赖

新建一个 Spring Boot 项目,核心依赖如下:

<dependencies> <!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- dynamic-datasource 多数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- ShardingSphere JDBC --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> </dependency> <!-- Lombok(可选,简化实体类) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

这里的版本号建议以实际项目 Maven 仓库可用的版本为准。ShardingSphere 的 starter 在不同小版本间 API 差异较大,如果遇到找不到类或方法签名不一致的问题,大概率是版本不匹配,优先检查 ShardingSphere、dynamic-datasource、MyBatis Plus 三者之间的兼容性。

2.3 项目目录结构

我建议按下面的结构组织代码,包名清晰,后续扩展更方便:

src/main/java/com/example/sqlbot ├── SQLBotApplication.java ├── config │ ├── DynamicDataSourceConfig.java │ └── ShardingDataSourceConfig.java ├── controller │ └── DataSourceMetaController.java ├── entity │ ├── DataSourceMeta.java │ └── TableMeta.java ├── mapper │ └── DataSourceMetaMapper.java ├── service │ ├── DataSourceMetaService.java │ └── impl/DataSourceMetaServiceImpl.java └── support ├── MetaDataLoader.java └── DataSourceRegistry.java

其中DataSourceMetaController负责暴露数据源导入接口,MetaDataLoader负责读取数据库字典信息,DataS源码Registry负责把新导入的数据源注册到动态数据源路由中。

3. 数据源导入备注的设计与实现

3.1 数据源元数据模型

数据源导入备注的第一步,是有一个合适的数据结构来承载数据源连接信息 + 业务备注。我们定义一个DataSourceMeta实体:

package com.example.sqlbot.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("sqlbot_datasource_meta") public class DataSourceMeta { /** 主键 */ @TableId(type = IdType.AUTO) private Long id; /** 数据源唯一标识,例如 order_db */ private String dataSourceKey; /** 数据库类型:mysql、postgresql、oracle */ private String dbType; /** 连接地址 */ private String host; /** 端口 */ private Integer port; /** 数据库名 */ private String databaseName; /** 用户名 */ private String username; /** 密码(生产环境务必加密存储) */ private String password; /** 数据源级备注:描述该库整体的业务范围 */ private String remark; /** 是否启用:1启用、0停用 */ private Integer enabled; /** 创建时间 */ private LocalDateTime createTime; /** 更新时间 */ private LocalDateTime updateTime; }

这里的remark字段就是“数据源级备注”,用来描述这个库整体的业务语义。比如“订单交易主库,包含订单、支付、退款等核心交易数据”。在 SQLBot 导入数据源时,这个备注应该作为必填项,而不是选填项。

对应的建表 SQL:

CREATE TABLE `sqlbot_datasource_meta` ( `id` bigint NOT NULL AUTO_INCREMENT, `data_source_key` varchar(64) NOT NULL COMMENT '数据源唯一标识', `db_type` varchar(32) NOT NULL COMMENT '数据库类型', `host` varchar(128) NOT NULL, `port` int NOT NULL, `database_name` varchar(128) NOT NULL, `username` varchar(128) NOT NULL, `password` varchar(256) NOT NULL, `remark` varchar(1024) DEFAULT NULL COMMENT '数据源备注,供SQLBot理解业务', `enabled` tinyint NOT NULL DEFAULT '1', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_datasource_key` (`data_source_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='SQLBot数据源元信息表';

注意data_source_key加了唯一索引,防止同一个数据源被重复导入。

3.2 表与字段注释读取

有了数据源级备注还不够,SQLBot 要真正理解业务,还需要表级注释和字段级注释。这部分信息在 MySQL 的information_schema中已经存在,我们可以在导入数据源时自动读取,无需用户手工维护。

具体来说,读取某张表的注释和字段注释,可以借助information_schema.columnsinformation_schema.tables。下面给出一个基于 JDBC 的元数据读取工具:

package com.example.sqlbot.support; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 读取数据库字典信息:表注释、字段注释、字段类型 */ @Slf4j @Component public class MetaDataLoader { public static class ColumnMeta { private String columnName; private String columnType; private String columnComment; // getter / setter 省略,建议使用 Lombok 简化 } public static class TableMeta { private String tableName; private String tableComment; private List<ColumnMeta> columns; // getter / setter 省略 } /** * 读取指定库下所有表的元数据 */ public List<TableMeta> loadTableMeta(String url, String username, String password, String databaseName) { List<TableMeta> result = new ArrayList<>(); String tableSql = "SELECT table_name, table_comment FROM information_schema.tables WHERE table_schema = ?"; String columnSql = "SELECT column_name, column_type, column_comment FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ordinal_position"; try (Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement tablePs = conn.prepareStatement(tableSql)) { tablePs.setString(1, databaseName); ResultSet tableRs = tablePs.executeQuery(); while (tableRs.next()) { TableMeta tableMeta = new TableMeta(); tableMeta.setTableName(tableRs.getString("table_name")); tableMeta.setTableComment(tableRs.getString("table_comment")); try (PreparedStatement columnPs = conn.prepareStatement(columnSql)) { columnPs.setString(1, databaseName); columnPs.setString(2, tableMeta.getTableName()); ResultSet columnRs = columnPs.executeQuery(); List<ColumnMeta> columns = new ArrayList<>(); while (columnRs.next()) { ColumnMeta columnMeta = new ColumnMeta(); columnMeta.setColumnName(columnRs.getString("column_name")); columnMeta.setColumnType(columnRs.getString("column_type")); columnMeta.setColumnComment(columnRs.getString("column_comment")); columns.add(columnMeta); } tableMeta.setColumns(columns); } result.add(tableMeta); } } catch (SQLException e) { log.error("读取数据库元数据失败", e); throw new RuntimeException("数据库元数据读取失败,请检查连接配置", e); } return result; } }

这段代码的作用是:连接目标数据库,查询information_schema下所有表和列的注释信息,组装成对象列表。之所以在“导入数据源”时就读取,是为了第一时间把这些元数据持久化下来,后续 SQLBot 做语义解析时可以直接读取,避免每次查询都去扫描一次字典。

如果你希望更轻量,也可以只读取用户选择的几张核心表,而不是全库扫描。全库扫描在某些库表数量极大时,会存在性能问题,建议在导入接口中增加“全量/部分”开关。

3.3 导入接口:连接、校验、保存

数据源导入接口的整体流程应该是:

  1. 接收前端传入的数据源配置和业务备注。
  2. 校验参数合法性,包括 Host、端口、库名是否为空。
  3. 使用 DTO 中的配置创建 JDBC 连接,连通性测试。
  4. 调用MetaDataLoader读取表与字段注释。
  5. 将连接信息、备注、表元数据统一持久化。
  6. 把该数据源注册到动态路由数据源中,供后续查询使用。

Controller 定义如下:

package com.example.sqlbot.controller; import com.example.sqlbot.entity.DataSourceMeta; import com.example.sqlbot.service.DataSourceMetaService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @RestController @RequestMapping("/api/datasource") public class DataSourceMetaController { @Resource private DataSourceMetaService dataSourceMetaService; @PostMapping("/import") public String importDataSource(@RequestBody DataSourceMeta dto) { dataSourceMetaService.importDataSource(dto); return "数据源导入成功"; } }

Service 层是核心,我建议把“校验、读取元数据、注册路由”三个步骤拆分成独立方法,便于单独测试:

package com.example.sqlbot.service.impl; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.example.sqlbot.entity.DataSourceMeta; import com.example.sqlbot.mapper.DataSourceMetaMapper; import com.example.sqlbot.service.DataSourceMetaService; import com.example.sqlbot.support.DataSourceRegistry; import com.example.sqlbot.support.MetaDataLoader; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.sql.DataSource; @Slf4j @Service public class DataSourceMetaServiceImpl implements DataSourceMetaService { @Resource private DataSourceMetaMapper dataSourceMetaMapper; @Resource private DataSourceRegistry dataSourceRegistry; @Resource private MetaDataLoader metaDataLoader; @Resource private DataSource dataSource; @Override public void importDataSource(DataSourceMeta dto) { // 1. 校验并测试连接 boolean connected = metaDataLoader.testConnection(buildUrl(dto), dto.getUsername(), dto.getPassword()); if (!connected) { throw new RuntimeException("数据源连接失败,请检查网络或账号权限"); } // 2. 读取元数据并持久化 // 这里简化处理,只保存连接配置 dataSourceMetaMapper.insert(dto); // 3. 注册到动态路由数据源 DataSource realDataSource = dataSourceRegistry.buildDataSource(dto); DynamicRoutingDataSource drds = (DynamicRoutingDataSource) dataSource; drds.addDataSource(dto.getDataSourceKey(), realDataSource); log.info("数据源导入成功:{},备注:{}", dto.getDataSourceKey(), dto.getRemark()); } private String buildUrl(DataSourceMeta dto) { return "jdbc:mysql://" + dto.getHost() + ":" + dto.getPort() + "/" + dto.getDatabaseName() + "?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; } }

MetaDataLoader中补一个testConnection方法:

public boolean testConnection(String url, String username, String password) { try (Connection conn = DriverManager.getConnection(url, username, password)) { return conn.isValid(5); } catch (SQLException e) { log.error("数据源连接测试失败: {}", e.getMessage()); return false; } }

建议在实际项目中把元数据读取结果也持久化到sqlbot_table_metasqlbot_column_meta两张表,而不是只保存连接配置。这样 SQLBot 做语义解析时可以直接读缓存表,性能更好,也方便后续人工修正备注。

4. Spring Boot + MyBatis Plus 多数据源实战

4.1 动态数据源配置

当系统中的数据源不再是一个,而是“主库 + 多个业务库”时,用dynamic-datasource是一个非常成熟的选择。它的核心思路是:在 Spring Boot 启动时构建一个DynamicRoutingDataSource,通过内部维护的数据源 Map 实现运行时切换,业务代码用@DS注解即可切换数据源。

application.yml中配置动态数据源:

spring: datasource: dynamic: # 默认数据源,如果不加 @DS 注解,默认走这里 primary: master # 是否严格模式:true 时未匹配到指定数据源会报错 strict: false datasource: master: url: jdbc:mysql://localhost:3306/sqlbot_meta?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver order: url: jdbc:mysql://localhost:3306/sqlbot_order?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver goods: url: jdbc:mysql://localhost:3306/sqlbot_goods?useSSL=false&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver

配置项说明:

  • primary表示默认数据源。对于 SQLBot 来说,主库通常保存数据源元信息、问数历史、备注字典等系统数据。
  • strict推荐设置为true。如果代码里@DS("order")但配置里没有order,严格模式会直接报错,方便早发现问题。
  • 每个子数据源的urlusernamepassworddriver-class-name分别对应实际库的连接信息。

4.2 使用 @DS 切换数据源

在 MyBatis Plus 的 Service 或 Mapper 上,直接加@DS注解即可切换数据源。

比如 SQLBot 首页需要展示“订单库的总订单量”,对应的查询逻辑走order数据源:

package com.example.sqlbot.service.impl; import com.baomidou.dynamic.datasource.annotation.DS; import com.example.sqlbot.mapper.OrderReportMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Map; @Service public class OrderQueryService { @Resource private OrderReportMapper orderReportMapper; /** * 查询订单库的总订单量与总金额 */ @DS("order") public Map<String, Object> queryOrderSummary() { return orderReportMapper.selectOrderSummary(); } }

对应的 Mapper:

package com.example.sqlbot.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.Map; @Mapper public interface OrderReportMapper { @Select("SELECT COUNT(*) AS orderCount, SUM(pay_amount) AS totalAmount FROM order_info") Map<String, Object> selectOrderSummary(); }

@DS注解可以加载在类上,也可以加载在方法上。方法上的注解优先级高于类。如果某个 Service 中大部分方法都走主库,只有个别方法需要切换,建议不加类级注解,只在指定方法上加。

4.3 多数据源下的 SQLBot 查询路由

SQLBot 在实际“问数”时,用户可能提问的是订单数据,也可能是商品数据、会员数据。通常的做法是:

  1. 通过意图识别判断问题所属的业务域。
  2. 映射到对应的dataSourceKey
  3. 使用@DS("xxx")或手动调用DynamicRoutingDataSource.determineDataSource()切换数据源。

我们可以封装一个数据源路由服务:

package com.example.sqlbot.support; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @Component public class QueryRouter { @Resource private DynamicRoutingDataSource dynamicRoutingDataSource; public Connection getConnection(String dataSourceKey) throws SQLException { DataSource dataSource = dynamicRoutingDataSource.getDataSource(dataSourceKey); if (dataSource == null) { throw new IllegalArgumentException("未找到数据源:" + dataSourceKey); } return dataSource.getConnection(); } }

这样做的好处是,即便没有走 MyBatis Plus 的 Mapper 层,也可以直接用 JDBC 执行动态 SQL。SQLBot 生成的 SQL 往往是运行时才拼好的,走 JDBC 是最直接的执行方式。但是要注意,QueryRouter拿到连接后必须手动关闭连接,防止连接泄漏。

5. 将 ShardingSphere 数据源注册到动态数据源

5.1 为什么需要手动注册

ShardingSphere JDBC 在启动时会创建一个ShardingSphereDataSource,它本身实现了DataSource接口,内部包含多个真实数据源和分片规则。但问题是,dynamic-datasource 的DynamicRoutingDataSource并不认识 ShardingSphere 创建的 DataSource,它只知道配置文件中显式列出的数据源。

如果业务方既需要多数据源切换能力,又需要对某个大数据表做分片,常见的做法就是:把 ShardingSphere 创建好的数据源,也作为一种“数据源”,注册到动态路由数据源中。这样上层代码既可以@DS("master")走普通数据源,也可以@DS("sharding_order")走分片数据源,对业务无感。

5.2 创建 ShardingSphere 数据源

我们可以在一个专门配置类中,创建 ShardingSphere 数据源并把它注册到DynamicRoutingDataSource

package com.example.sqlbot.config; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory; import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration; import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration; import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration; import org.apache.shardingsphere.sharding.api.config.strategy.keygen.KeyGenerateStrategyConfiguration; import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; @Slf4j @Configuration public class ShardingDataSourceConfig { @Resource private DataSource dataSource; @PostConstruct public void registerShardingDataSource() throws Exception { // 1. 真实数据源:订单库的两个分片 Map<String, DataSource> actualDataSources = new HashMap<>(); actualDataSources.put("ds_0", createDataSource( "jdbc:mysql://localhost:3306/order_0?useSSL=false&serverTimezone=Asia/Shanghai", "root", "root")); actualDataSources.put("ds_1", createDataSource( "jdbc:mysql://localhost:3306/order_1?useSSL=false&serverTimezone=Asia/Shanghai", "root", "root")); // 2. 分片规则 ShardingRuleConfiguration ruleConfig = new ShardingRuleConfiguration(); ShardingTableRuleConfiguration orderTableRule = new ShardingTableRuleConfiguration("order_info", "ds_${0..1}.order_info_${0..1}"); orderTableRule.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("id", "snowflake")); orderTableRule.setTableShardingStrategy(new StandardShardingStrategyConfiguration("user_id", "table_inline")); orderTableRule.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("user_id", "db_inline")); ruleConfig.getTables().add(orderTableRule); Properties props = new Properties(); // 3. 创建 ShardingSphere 数据源 DataSource shardingDataSource = ShardingSphereDataSourceFactory.createDataSource( actualDataSources, Collections.singletonList(ruleConfig), props ); // 4. 注册到动态路由数据源 DynamicRoutingDataSource drds = (DynamicRoutingDataSource) dataSource; drds.addDataSource("sharding_order", shardingDataSource); log.info("ShardingSphere 数据源已注册到动态数据源,key=sharding_order"); } private DataSource createDataSource(String url, String username, String password) { // 这里的实现取决于你使用的连接池,推荐 HikariCP HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setJdbcUrl(url); hikariConfig.setUsername(username); hikariConfig.setPassword(password); hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); return new HikariDataSource(hikariConfig); } }

注意:ShardingSphere 5.x 的AlgorithmConfiguration定义方式在不同小版本中略有差异。上面代码里的StandardShardingStrategyConfigurationKeyGenerateStrategyConfiguration在 5.3.x 版本中可用,如果你用的是 5.1 或 5.2,请按官方文档调整。关键思路一致:构建ShardingRuleConfiguration,然后通过ShardingSphereDataSourceFactory.createDataSource创建数据源对象。

上面的分片规则含义是:

  • 物理库:ds_0ds_1
  • 逻辑表order_info实际映射到ds_${0..1}.order_info_${0..1},即每个库下面还有两张分表。
  • 分库字段:user_id,分表字段:user_id
  • 主键策略:雪花算法。

这段配置是典型的分库分表配置,具体分片数量和分片策略需要根据业务量调整。

5.3 注册到 DynamicRoutingDataSource

第 5.2 节中已经演示了注册的代码,核心就两行:

DynamicRoutingDataSource drds = (DynamicRoutingDataSource) dataSource; drds.addDataSource("sharding_order", shardingDataSource);

这里需要注意一个细节:DynamicRoutingDataSource持有的dataSourceMap在运行时是可变的,addDataSource方法会把新的数据源放到内部 Map 中。因此,即使你的数据源不在application.yml中配置,只要运行期注册进去,就能通过@DS("sharding_order")动态切换。

注册完成后,之前的QueryRouter也可以直接获取分片数据源:

public Connection getShardingConnection() throws SQLException { DataSource shardingDataSource = dynamicRoutingDataSource.getDataSource("sharding_order"); return shardingDataSource.getConnection(); }

这样一来,SQLBot 在生成 SQL 时,遇到涉及订单分片表的查询,只需要指定dataSourceKey = "sharding_order",执行层就会自动走 ShardingSphere 的路由逻辑,把逻辑 SQL 改写成真实分片 SQL。

5.4 验证 ShardingSphere 与动态数据源结合

写一个测试接口验证注册结果:

package com.example.sqlbot.controller; import com.example.sqlbot.support.QueryRouter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/api/test") public class TestController { @Resource private QueryRouter queryRouter; @GetMapping("/sharding") public List<String> testSharding() throws Exception { List<String> result = new ArrayList<>(); try (Connection conn = queryRouter.getConnection("sharding_order"); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT COUNT(*) AS cnt FROM order_info")) { while (rs.next()) { result.add("分片订单总数:" + rs.getLong("cnt")); } } return result; } }

如果 ShardingSphere 数据源注册成功,访问/api/test/sharding时,SQL 会经过分片引擎改写,实际在多个分表上执行,并聚合返回结果。这一步能验证整个链路是否打通。

6. 常见问题与排查思路

在数据源导入备注、多数据源切换、ShardingSphere 注册这套组合场景中,高频问题主要集中在几个方向。下面用表格梳理一下:

问题现象常见原因解决思路
数据源导入时连接失败端口不可达、账号密码错误、MySQL 驱动不匹配先用数据库客户端手工连接一次,排除网络与账号问题
导入时读取不到表注释连接账号缺少information_schema读取权限给账号授权SELECT权限,或调整元数据读取方式
@DS("order")不生效,仍然走主库注解加在类上但方法上没有,或方法参数未通过代理调用确认注解所在层级与数据源 key 完全匹配,检查调用方是否加@DS
动态数据源找不到 ShardingSphere 数据源addDataSource未执行,或注册时顺序在 Spring Bean 初始化之前确认@PostConstruct执行成功,检查日志中是否有注册成功记录
ShardingSphere 启动报错版本不兼容、分片规则表达式错误、物理表不存在先简化分片规则,确保分片表达式能映射到已有表,再逐步还原复杂配置
SQLBot 生成 SQL 中表名不存在逻辑表名与物理表名不一致检查 ShardingSphere 逻辑表配置,确保查询语句使用的是逻辑表名
注册的动态数据源在重启后消失addDataSource只在内存中生效,未持久化如果 key 是动态导入的,需要在每次启动时重新注册
数据库密码明文存在库里安全规范问题采用接密件、KMS 或配置中心加密,最小权限原则

排查动态数据源问题时,最实用的手段是打开dynamic-datasource的调试日志:

logging: level: com.baomidou.dynamic.datasource: debug

日志中会打印每次请求路由到了哪个数据源,可以快速判断是注解问题还是数据源注册问题。

7. 最佳实践与工程建议

7.1 备注体系要“导入时强约束,使用中可修正”

数据源导入备注最怕的是“用户不填、系统不查”。我的建议是:

  • 数据源级remark设置为必填项,导入时校验非空且不少于一定字数。
  • 表级、字段级备注不强制手工录入,而是优先从information_schema自动读取;对于没有注释的字段,在 SQLBot 管理后台提供“批量补充注释”的功能。
  • 允许业务人员在后台修正、补充备注,并且每次修正都要记录审计日志,方便追踪“为什么某个 SQL 突然变了”。

备注质量是智能问数的核心资产,宁可导入时慢 1 秒,也要把元数据抓全。

7.2 多数据源 key 的命名规范

数据源key相当于数据源的唯一标识,命名不规范会导致 SQLBot 意图识别错乱。推荐命名规范:

  • 业务域前缀 + 下划线 + 库类型后缀,例如order_dbgoods_dbmember_db
  • 分片数据源以sharding_开头,例如sharding_order
  • 禁止使用db1db2这类无业务含义的命名。

命名规范一旦确定,最好在导入接口中做正则校验,从源头避免脏数据。

7.3 密码加密与最小权限

本文示例中密码直接放在 DTO 和实体中,仅用于演示。生产环境必须做到:

  • 数据库账号按业务域隔离,SQLBot 只授予只读权限。
  • 密码字段加密存储,不落明文日志。
  • 连接配置统一放到配置中心(Nacos、Apollo),动态刷新,不用重新部署。

权限的最小化原则同样适用于 ShardingSphere:分片数据源账号只需要SELECT权限即可。

7.4 动态注册的数据源要支持启动自愈

DynamicRoutingDataSource.addDataSource是内存级操作。如果数据源 key 是通过管理后台导入的,重启后需要重新注册。建议在启动时做一次“数据源元信息对比”:

  1. sqlbot_datasource_meta表查询所有启用的数据源。
  2. 遍历判断是否已存在于DynamicRoutingDataSource
  3. 如果缺失,则重新构建连接并注册。

这样才能保证停机维护后,动态数据源不会出现“配置丢失”的假象。

8. 总结与学习路线

本文围绕“SQLBot 数据源导入备注,智能问数更清晰”这个核心话题,完整覆盖了三个层面的内容:

第一,我们解释了 SQLBot 这类智能问数工具为什么高度依赖元数据,并把数据源备注拆解为数据源级、表级、字段级三个层级。备注不是给用户看的文档,而是给 NLP 模型吃的上下文,质量越高,生成 SQL 的准确率越高。

第二,我们通过一个完整的 Spring Boot + MyBatis Plus 项目,实现了数据源导入接口、表注释/字段注释读取、动态数据源切换。这套代码可以直接作为 SQLBot 数据源管理模块的底座。

第三,我们解决了一个工程难点:如何把 ShardingSphere 创建的数据源注册到 dynamic-datasource 的动态路由中。这样 SQLBot 既能兼容普通多数据源,也能支持分库分表的复杂场景。

下一步你可以继续深入的方向有:

  • 基于 OpenAI 或开源大模型,把本文的数据源备注体系接入 Prompt 模板,做真实的自然语言转 SQL 实验。
  • 为核心业务表补充更细粒度的字段枚举备注,构建行业字典。
  • 研究 ShardingSphere 分片键选择、分布式主键、读写分离等更高级配置。
  • 为数据源管理后台增加“备注质量分”评估,提醒用户哪些表备注不足。

如果你正在做智能问数、数据中台或 BI 增强产品,数据源备注都是一个值得投入的基础工程。先把元数据打牢,再谈模型调优,效果会事半功倍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 2:02:08

YOLOv12+PyQt5交通应急车辆识别实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 2:01:13

Android智慧医疗预约挂号App毕业设计:全栈开发实战指南

简介&#xff1a;本资源是一套面向计算机专业本科生的毕业设计级智慧医疗应用实战案例&#xff0c;聚焦医院预约挂号核心业务&#xff0c;适用于Android移动开发课程设计、毕设选题与健康医疗类App开发能力提升。项目采用Android Studio原生开发&#xff0c;包含完整客户端与轻…

作者头像 李华
网站建设 2026/9/3 1:59:09

Python数据分析实战:演唱会热度与粉丝行为可视化项目全解析

简介&#xff1a;本资源是一份面向Python数据分析初学者与高校课程设计者的综合性大作业实践项目&#xff0c;聚焦演唱会市场这一典型文化消费场景&#xff0c;解决从数据采集、清洗、分布式分析到Web可视化落地的全流程问题。压缩包共65个文件&#xff0c;约11.86MB&#xff0…

作者头像 李华
网站建设 2026/9/3 1:58:59

MiniMax H3 新更新:官方 Skills 与 Turbo Lora 本地部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 1:58:23

从RAG到智能体:构建动态交互式检索增强问答系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 1:57:57

嵌入式SD卡驱动与FATFS文件系统移植实战:从HC32F100源码解析到工程优化

简介&#xff1a;本资源是基于华大HD100四合一读卡器的C#开发参考源码包&#xff0c;面向Windows平台下进行身份证、社保卡、健康卡及就诊卡集成读取的软硬件开发者与嵌入式应用工程师。项目通过C#调用封装好的C DLL动态库实现多卡协议兼容&#xff0c;涵盖底层通信、数据解析与…

作者头像 李华