应用案例 · 源码级分析

分布式多租户管理框架

RuoYi-Vue-Plus 多数据源代码生成深度解析

基于 Dromara RuoYi-Plus源码分析,揭示 AnyLine 如何通过 DataSourceMonitor 适配 DynamicRoutingDataSource, 实现多租户场景下跨数据源代码生成。

DataSourceMonitor DynamicRoutingDataSource metadata().tables() 多租户异构数据库 Dromara 生态
16.4K
Star 数(Dromara)
3
DataSourceMonitor API
2 类
元数据查询入口
1 个
注解驱动数据源切换
1

项目背景:RuoYi-Vue-Plus × AnyLine

Dromara 开源生态 × 多租户分布式场景

🦁
RuoYi-Vue-Plus
Dromara · MIT License
• 重写 RuoYi-Vue,专为分布式集群 + 多租户场景
• Sa-Token 鉴权 / MyBatis-Plus ORM / dynamic-datasource
• 原生支持 MySQL、Oracle、PostgreSQL、SQLServer
• 达梦 DM、人大金仓等国产数据库均可通过 AnyLine 接入
• 代码生成器支持动态多数据源代码生成(异构切换)
🔌
AnyLine MDM
元数据驱动 · 数据库适配层
DataSourceMonitor:桥接 AnyLine 与 DynamicRoutingDataSource
ServiceProxy.metadata():统一元数据查询接口
• 多租户场景:每个租户可能连接不同数据库,AnyLine 统一抽象
• 代码生成器核心:跨数据源表结构解析 + 字段元数据提取
用户操作 配置/生成代码 GenTableServiceImpl @DS("#dataName") 切换 metadata().tables() / table() Velocity 代码生成 MyBatisDataSourceMonitor feature() → adapter 识别 key() → ds 名称 keepAdapter() → 是否复用 AnyLine Core Adapter 自动选择 metadata() 方言适配 DDL / DML 执行 DB MySQL PG DM Oracle
2

核心能力一:DataSourceMonitor 桥接 AnyLine 与 DynamicRoutingDataSource

实现 DataSourceMonitor 接口的三件事

让 AnyLine 能够识别 baomidou DynamicRoutingDataSource 中的动态数据源及其对应的数据库类型

🔍
feature() — 识别数据库类型
从 DatabaseMetaData 提取产品名 + URL,定位 AnyLine Adapter
🪪
key() — 数据源唯一标识
从 DynamicDataSourceContextHolder 获取当前数据源名称
⚙️
keepAdapter() — 适配器复用策略
KEEP_ADAPTER=2:按接口动态判断,同一 DS 未必复用同一 Adapter
① feature() 识别适配器
// 从 JDBC URL 反推数据库产品名 + 拼装 adapter key
public String feature(DataRuntime, Object ds) {
JdbcTemplate jdbc = (JdbcTemplate) ds;
DataSource ds = jdbc.getDataSource();
// 从上下文获取当前数据源名称
String key = DynamicDataSourceContextHolder.peek();
// 缓存 key → feature 的映射
Connection con = ds.getConnection();
DatabaseMetaData meta = con.getMetaData();
String url = meta.getURL();
// 拼装: "mysql_ jdbc:mysql://..."
return productName + "_" + url;
}
② key() 数据源标识
// 返回数据源唯一标识(名称)
public String key(DataRuntime, Object ds) {
if (ds instanceof JdbcTemplate jdbc) {
DataSource ds = jdbc.getDataSource();
if (ds instanceof DynamicRoutingDataSource) {
return DynamicDataSourceContextHolder.peek();
}
}
// 非动态数据源时使用默认 key
return runtime.getKey();
}
③ keepAdapter() 复用策略
// KEEP_ADAPTER=2 时被调用:是否复用同一个 Adapter
public boolean keepAdapter(DataRuntime, Object ds) {
DataSource real = ((JdbcTemplate) ds).getDataSource();
// DynamicRoutingDataSource 不复用,
// 因为同一个路由 DS 可能对应多类数据库
return !(real instanceof DynamicRoutingDataSource);
}
// 构造器中的关键配置
public class MyBatisDataSourceMonitor implements DataSourceMonitor {
public MyBatisDataSourceMonitor() {
// ① 调整为自定义执行模式,由 Monitor 全权控制
ConfigTable.KEEP_ADAPTER = 2;
// ② 禁用元数据缓存,每次查询实时拉取
ConfigTable.METADATA_CACHE_SCOPE = 0;
}
}
3

核心能力二:跨数据源元数据查询

GenTableServiceImpl — 两类元数据 API,配合 @DS 注解驱动数据源切换

API 1 selectPageDbTableList — @DS("#genTable.dataName") 分页查询所有表
// @DS 注解:动态切换到目标数据源
@DS("#genTable.dataName")
public TableDataInfo<GenTable> selectPageDbTableList(...) {
// metadata().tables() — 获取当前数据源下的所有表
LinkedHashMap<String, Table<?>> tables =
ServiceProxy.metadata().tables();
// 过滤系统表、已导入表;按名称/注释模糊搜索
List<GenTable> tables = tablesMap.values().stream()
.filter(...).map(x -> {
GenTable gen = new GenTable();
gen.setTableName(x.getName());
gen.setTableComment(x.getComment());
gen.setCreateTime(x.getCreateTime());
}).toList();
}
API 2 selectDbTableColumnsByName — 解析表的所有字段元数据
// metadata().table(name) → 提取列信息,映射为 GenTableColumn
@DS("#dataName")
public List<GenTableColumn> selectDbTableColumnsByName(...) {
Table<?> table = ServiceProxy.metadata().table(tableName);
LinkedHashMap<String, Column> cols = table.getColumns();
cols.forEach((name, col) -> {
GenTableColumn c = new GenTableColumn();
c.setIsPk(col.isPrimaryKey() ? "1" : "0");
c.setColumnName(col.getName());
c.setColumnComment(col.getComment());
c.setColumnType(col.getOriginType());
c.setIsRequired(col.isNullable() ? "0" : "1");
c.setIsIncrement(col.isAutoIncrement() ? "1" : "0");
c.setSort(col.getPosition());
});
}
API 3 selectDbTableListByNames — 根据表名批量查询表元数据
// 批量导入场景:传入多个表名,一次性查询所有表的元数据
@DS("#dataName")
public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
LinkedHashMap<String, Table<?>> tablesMap =
ServiceProxy.metadata().tables();
Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
tablesMap.values().stream()
.filter(x -> tableNameSet.contains(x.getName()))
.map(x -> { ... }).toList();
}
4

MyBatis Plus × AnyLine:动静分离的数据层

MyBatis Plus 平台配置表 CRUD
GenTable — 代码生成的表配置
GenTableColumn — 字段配置(查询方式/字典类型)
GenDatasourceConf — 数据源连接配置
baseMapper.selectPage() — 分页查询配置表
Velocity 模板渲染 → Java / HTML / SQL 代码文件
AnyLine 多数据源元数据
metadata().tables() — 浏览数据源下所有表
metadata().table(name) — 查询单表结构
table.getColumns() — 提取列(名/类型/主键/注释)
DataSourceMonitor — 适配 DynamicRoutingDataSource
运行时元数据,MyBatis Plus 管配置,AnyLine 管目标库
用户操作 选表/导入 @DS("#dataName") MyBatis Plus: GenTable CRUD AnyLine: metadata().tables() / table() DataSourceMonitor feature() → Adapter 识别 AnyLine 执行元数据查询 目标数据库 MySQL / PG / Oracle / 达梦 DynamicRoutingDataSource 路由
5

案例总结

AnyLine 在 RuoYi-Vue-Plus 中的定位

多租户异构数据库的统一元数据抽象层

🔌
DataSourceMonitor
桥接 AnyLine 与 baomidou 动态数据源
🌐
多数据源元数据
metadata() 统一查询接口
📋
跨租户代码生成
每个租户连不同数据库均可生成代码
异构数据库切换
@DS 注解 + AnyLine = 零 SQL 跨库查询

💡 AnyLine 价值

DataSourceMonitor 接口的标杆实现:RuoYi-Vue-Plus 通过实现 DataSourceMonitor 接口,让 AnyLine 能够与 baomidou DynamicRoutingDataSource 无缝配合,这是 AnyLine 适配各类动态数据源框架的标准范式。

多租户场景的元数据适配:多租户系统中,每个租户可能使用不同的数据库(MySQL / PostgreSQL / 达梦),AnyLine 统一抽象了元数据查询接口,业务代码无需感知底层差异。

🎯 可复制的技术路径

动态数据源 + AnyLine 集成模板:MyBatisDataSourceMonitor 是 AnyLine 与 baomidou dynamic-datasource 集成的标准参考,任何使用 dynamic-datasource 的项目均可参考此模式。

代码生成器的最优数据层架构:@DS 注解 + MyBatis Plus + AnyLine metadata() 三者配合,实现真正的多数据源代码生成,业务代码完全零 SQL。

源码分析来源
• MyBatisDataSourceMonitor.java — DataSourceMonitor 实现,桥接 AnyLine 与 DynamicRoutingDataSource
• GenTableServiceImpl.java — AnyLine metadata() 元数据查询(tables / table / columns),Velocity 代码生成
• 项目主页:RuoYi-Vue-Plus(16.4K ⭐,Dromara)

分享案例获取系统性的技术支持

每一次数据库成功切换的背后,都有一段充满技术挑战与架构演进的故事