1. RuoYi-Vue-Plus多数据源配置概述
在现代企业级应用开发中,多数据源支持已成为刚需。RuoYi-Vue-Plus作为基于Spring Boot和Vue.js的快速开发框架,通过集成dynamic-datasource-spring-boot-starter组件,为开发者提供了简洁高效的多数据源解决方案。这套方案的核心价值在于:只需通过YAML配置声明数据源,再配合@DS注解即可实现数据源动态切换,无需编写繁琐的底层连接管理代码。
我在实际项目中使用该方案时发现,它特别适合以下场景:
- 读写分离架构(主库写,从库读)
- 多租户系统中不同租户数据隔离
- 需要同时访问多种异构数据库(如MySQL+Oracle)
- 分库分表场景下的数据路由
与传统的AbstractRoutingDataSource方案相比,dynamic-datasource具有配置简单、切换灵活、监控完善等优势。其底层采用HikariCP连接池,默认提供了连接泄漏检测、空闲连接回收等生产级特性,这在处理高并发请求时尤为重要。
2. 多数据源详细配置指南
2.1 基础环境准备
在开始配置前,请确保项目满足以下条件:
- JDK 1.8+
- Maven 3.5+
- RuoYi-Vue-Plus 5.x版本
- 已正确配置Spring Boot和MyBatis-Plus
提示:建议使用IDE的YAML插件(如IntelliJ IDEA的YAML/Ansible支持)来编辑配置文件,避免缩进错误导致的解析问题。
2.2 数据源配置文件详解
在application.yml中配置多数据源时,需要重点关注spring.datasource.dynamic节点。以下是一个生产级配置示例,包含主从库和异构数据库:
yaml复制spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
dynamic:
primary: master # 默认数据源
strict: true # 启用严格模式
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://master.db:3306/core_db?useSSL=false&allowPublicKeyRetrieval=true
username: app_user
password: ${DB_MASTER_PWD:default_pwd}
hikari:
pool-name: MASTER_POOL
maximum-pool-size: 30
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
validation-timeout: 5000
connection-test-query: SELECT 1
slave1:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://slave1.db:3306/core_db?useSSL=false
username: app_ro
password: ${DB_SLAVE_PWD:default_pwd}
hikari:
pool-name: SLAVE1_POOL
maximum-pool-size: 20
oracle_report:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@//report.db:1521/ORCL
username: report_user
password: ${DB_ORACLE_PWD}
hikari:
max-lifetime: 1200000 # Oracle连接建议缩短生命周期
关键配置项说明:
- primary:必须指定一个存在的数据源作为默认值
- strict模式:生产环境强烈建议开启,可避免配置错误导致的静默失败
- 密码安全:使用${}引用环境变量,避免密码硬编码
- 连接池调优:
- maximum-pool-size = CPU核心数 * 2 + 有效磁盘数
- 从库可设置lazy:true延迟初始化
- Oracle需要特殊调整max-lifetime
2.3 多数据源初始化流程
当应用启动时,dynamic-datasource会按以下顺序初始化:
- 解析YAML配置,创建DataSourceBuilder
- 根据primary设置确定主数据源
- 非lazy数据源立即初始化连接池
- 注册动态数据源到Spring上下文
- 初始化事务管理器(需特别注意与MyBatis-Plus的兼容性)
3. 数据源切换实战技巧
3.1 注解式切换最佳实践
@DS注解支持方法级和类级使用,以下是几种典型用法:
java复制@Service
@DS("slave1") // 类级别默认数据源
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@DS("master") // 写操作切到主库
@Transactional
public void addUser(User user) {
userMapper.insert(user);
// 注意:同一方法内无法再切换数据源
}
@Override
public List<User> listUsers() {
// 使用类注解的slave1数据源
return userMapper.selectList(null);
}
@DS("#tenantId") // 动态数据源,从参数获取
public List<User> listByTenant(String tenantId) {
return userMapper.selectList(
new QueryWrapper<User>().eq("tenant_id", tenantId));
}
}
使用注意事项:
- 事务边界:@Transactional会锁定初始数据源
- SpEL表达式:动态数据源名称需确保存在
- 继承问题:父类@DS不会被子类继承
- AOP顺序:自定义切面需设置@Order确保在@DS之前执行
3.2 编程式切换高级用法
对于需要复杂路由逻辑的场景,可以使用DynamicDataSourceContextHolder:
java复制public class OrderService {
public void processOrder(Long orderId) {
// 1. 在主库查询订单
Order order = orderMapper.selectById(orderId);
// 2. 切换到报表库生成分析数据
DynamicDataSourceContextHolder.push("oracle_report");
try {
salesReportService.generate(order);
} finally {
DynamicDataSourceContextHolder.poll();
}
// 3. 自动恢复主库上下文
order.setStatus("PROCESSED");
orderMapper.updateById(order);
}
}
编程式切换的典型场景包括:
- 批量处理需要轮询多个数据源
- 根据运行时条件动态选择数据源
- 在第三方库代码中强制使用特定数据源
3.3 多租户数据隔离方案
结合dynamic-datasource实现多租户:
java复制@Aspect
@Component
public class TenantDataSourceAspect {
@Before("execution(* com..service.*.*(..)) && args(..,tenantId)")
public void before(JoinPoint jp, String tenantId) {
if (StringUtils.isNotBlank(tenantId)) {
DynamicDataSourceContextHolder.push("tenant_" + tenantId);
}
}
@AfterReturning("execution(* com..service.*.*(..))")
public void after() {
DynamicDataSourceContextHolder.clear();
}
}
配套需要在启动时动态注册数据源:
java复制@Configuration
public class TenantDataSourceConfig {
@Autowired
private DataSourceProperties properties;
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
return new AbstractDataSourceProvider() {
@Override
public Map<String, DataSource> loadDataSources() {
Map<String, DataSource> map = new HashMap<>();
// 加载固定数据源
map.putAll(YamlDynamicDataSourceProvider.loadDataSources(properties));
// 动态添加租户数据源
tenantService.list().forEach(tenant -> {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(tenant.getJdbcUrl());
// ...其他配置
map.put("tenant_"+tenant.getId(), ds);
});
return map;
}
};
}
}
4. 生产环境问题排查
4.1 典型问题及解决方案
问题1:事务内切换失效
现象:在@Transactional方法中使用@DS无效
原因:Spring事务管理器会在事务开始时绑定数据源
解决:
java复制@DS("slave")
@Transactional(propagation = Propagation.NOT_SUPPORTED) // 暂停当前事务
public List<User> search(String keyword) {
return userMapper.selectList(
new QueryWrapper<User>().like("name", keyword));
}
问题2:连接泄漏
现象:应用运行一段时间后连接耗尽
排查:
- 在配置中添加leak-detection-threshold: 60000(1分钟)
- 监控hikari.pool.Master_POOL.activeConnections
- 检查未关闭的ResultSet/Statement
问题3:从库延迟
现象:主库写入后从库查询不到
方案:
java复制@DS("master")
public void createOrder(Order order) {
orderMapper.insert(order);
// 强制后续操作使用主库
DynamicDataSourceContextHolder.push("master");
}
4.2 监控与调优建议
-
监控指标:
- 各池active/idle/total连接数
- 获取连接平均耗时
- 最大等待线程数
-
性能调优参数:
yaml复制hikari: connection-timeout: 250 # 适当降低超时快速失败 initialization-fail-timeout: 1 # 启动时连接失败超时(秒) keepalive-time: 30000 # 保活间隔(ms) -
诊断工具:
- /actuator/datasource - 查看数据源状态
- HikariCP日志级别设为DEBUG
- SkyWalking/Druid监控SQL执行
5. 进阶扩展方案
5.1 自定义数据源选择策略
实现DataSourceSelector接口可定制路由逻辑:
java复制public class ReadWriteDataSourceSelector implements DataSourceSelector {
private static final List<String> READ_METHODS =
Arrays.asList("select", "get", "query", "find");
@Override
public String determineDataSource(MethodInvocation invocation) {
String methodName = invocation.getMethod().getName();
boolean isRead = READ_METHODS.stream()
.anyMatch(methodName::startsWith);
return isRead ? "slave" : "master";
}
}
注册选择器:
java复制@Configuration
public class DataSourceConfig {
@Bean
public DataSourceSelector readWriteSelector() {
return new ReadWriteDataSourceSelector();
}
}
5.2 多数据源事务管理
使用ChainedTransactionManager实现跨库事务:
java复制@Bean
public PlatformTransactionManager transactionManager(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
return new ChainedTransactionManager(
new DataSourceTransactionManager(master),
new DataSourceTransactionManager(slave)
);
}
注意事项:
- 不能保证真正的ACID
- 提交顺序与声明顺序相反
- 建议配合补偿机制使用
5.3 动态注册数据源
运行时添加新数据源:
java复制@Autowired
private DynamicRoutingDataSource routingDataSource;
public void addTenantDataSource(Tenant tenant) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(tenant.getJdbcUrl());
// ...其他配置
routingDataSource.addDataSource("tenant_"+tenant.getId(), ds);
// 触发刷新事件
context.publishEvent(new DataSourceInitEvent(this));
}
我在实际项目中总结的经验是:对于频繁创建销毁的临时数据源,建议配合连接池的eviction参数使用,避免长期占用资源。同时要注意线程安全问题,新增数据源时应加锁保护。