告别单数据库在RuoYi(若依)SpringBoot项目中优雅集成PostgreSQL作为第二数据源当业务系统从单一模块发展为多业务线协同运作时数据库架构往往会面临关键转折点。最近在重构某人力资源SaaS平台时我们遇到了一个典型场景核心业务数据存储在MySQL中而新收购的考勤分析模块却基于PostgreSQL构建。本文将分享如何在RuoYi框架中实现两种数据库引擎的和谐共存。1. 多数据源架构的决策逻辑在考虑集成PostgreSQL之前团队内部曾激烈讨论过三种方案全量迁移至PostgreSQL、数据同步到MySQL、以及多数据源并行。最终选择多数据源方案基于以下考量技术债务控制遗留系统包含大量存储过程和自定义类型迁移成本高达137人天性能隔离需求考勤分析模块的OLAP查询会占用70%的数据库资源技术栈适配性GIS空间查询在PostgreSQL中性能比MySQL快4-8倍提示当评估是否采用多数据源时建议用TCO(总体拥有成本)模型计算3年内的维护成本数据库引擎特性对比特性MySQL 8.0PostgreSQL 14事务隔离级别4种6种JSON支持有限完整地理空间索引基础专业并发写入性能优极优存储过程复杂度简单复杂2. Druid连接池的精细化配置Alibaba Druid在管理多数据源时展现出独特优势。以下是针对混合环境的配置要点# application-druid.yml 关键配置 spring: datasource: druid: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://mysql-cluster:3306/hr_core initial-size: 5 max-active: 50 validation-query: SELECT 1 FROM dual attendance: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://pg-replica:5432/attendance initial-size: 10 # PostgreSQL连接建立成本较高 max-active: 30 validation-query: SELECT 1 filters: stat,wall特别注意PostgreSQL的JDBC驱动对连接泄漏敏感建议比MySQL设置更短的remove-abandoned-timeout两种数据库的验证查询语法不同MySQL用FROM dual监控界面需区分统计两个数据源Bean public ServletRegistrationBeanStatViewServlet druidServlet() { ServletRegistrationBeanStatViewServlet reg new ServletRegistrationBean(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings(/druid/*); // 分别监控两个数据源 reg.addInitParameter(allow,mysql-monitor.pg-monitor); return reg; }3. 扩展RuoYi动态数据源机制RuoYi内置的DynamicDataSource类需要做以下增强public class EnhancedDynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocalString CONTEXT_HOLDER new ThreadLocal(); // 新增数据源注册方法 public synchronized void addDataSource(String dsName, DataSource dataSource) { MapObject, Object targetSources getTargetDataSources(); if (!targetSources.containsKey(dsName)) { targetSources.put(dsName, dataSource); setTargetDataSources(targetSources); initialize(); // 必须重新初始化 } } Override protected Object determineCurrentLookupKey() { String dsKey CONTEXT_HOLDER.get(); // 默认路由到主库 return StringUtils.isEmpty(dsKey) ? DataSourceType.MASTER.name() : dsKey; } }使用时需要注意的陷阱PostgreSQL的schema概念与MySQL不同需要在SQL中显式指定字段类型映射需特别注意PostgreSQL的serial对应MySQL的auto_incrementtimestamp with time zone需要特殊处理4. 跨库事务的解决方案在同时操作两个数据库的场景下我们采用最终一致性模式替代分布式事务Service public class AttendanceImportService { DataSource(DataSourceType.MASTER) Transactional public void syncEmployeeData(EmployeeDTO dto) { // 1. 在主库记录操作日志 operationLogMapper.insert(buildLog(dto)); try { // 2. 切换到PostgreSQL执行操作 processInPg(dto); } catch (Exception e) { // 3. 补偿机制 compensationHandler.scheduleRetry(dto); throw new BusinessException(同步失败已进入补偿流程); } } DataSource(DataSourceType.ATTENDANCE) void processInPg(EmployeeDTO dto) { // PostgreSQL特有操作 pgEmployeeMapper.upsert(dto); pgStatsMapper.updateDepartmentStats(dto.getDeptId()); } }关键补偿表结构设计-- MySQL中的补偿任务表 CREATE TABLE sync_compensation ( task_id varchar(36) NOT NULL, payload json NOT NULL, retry_count int DEFAULT 0, next_retry_time datetime DEFAULT NULL, status enum(pending,processing,failed) DEFAULT pending, PRIMARY KEY (task_id), KEY idx_status_retry (status,next_retry_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;5. 性能优化实战技巧经过三个月生产环境验证我们总结了这些关键优化点连接池配置黄金法则MySQL连接数 (核心数 * 2) 有效磁盘数PostgreSQL连接数 (核心数) (磁盘数 * 0.5)混合环境SQL编写规范// 错误示例 - 使用MySQL特有的LIMIT语法 DataSource(DataSourceType.ATTENDANCE) Select(SELECT * FROM attendance_records LIMIT 10) ListRecord findRecent(); // 正确写法 - 使用标准SQL DataSource(DataSourceType.ATTENDANCE) Select(SELECT * FROM attendance_records FETCH FIRST 10 ROWS ONLY) ListRecord findRecentStandard();监控指标重点关注PostgreSQL的idle_in_transaction连接数MySQL的threads_running与threads_connected比值两个连接池的wait_thread_count变化趋势在灰度发布阶段我们通过以下命令实时观察性能表现# 监控PostgreSQL连接状态 watch -n 1 psql -c SELECT state,count(*) FROM pg_stat_activity GROUP BY state # MySQL性能观测 mysqladmin -h mysql-cluster -uroot -p ext -i1 | grep -E Threads_running|Queries实际项目中最大的惊喜是PostgreSQL的窗口函数极大简化了考勤统计逻辑原本需要Java处理的复杂分组计算现在用一条SQL就能完成SELECT employee_id, sum(hours) OVER (PARTITION BY department ORDER BY work_date) AS cumulative_hours, rank() OVER (PARTITION BY department ORDER BY hours DESC) AS dept_rank FROM attendance WHERE work_date BETWEEN 2023-01-01 AND 2023-12-31