1. 多数据源架构设计思路在实际企业级应用中混合使用多种数据库已经成为常态。比如核心业务数据使用MySQL存储而地理信息系统(GIS)或分析模块则更适合PostgreSQL。若依框架通过动态数据源机制完美支持这种混合架构我最近在一个物流管理系统中就采用了这种方案。多数据源的核心原理是通过AbstractRoutingDataSource实现动态路由。简单来说它就像一个智能路由器根据我们设置的标识来决定使用哪个数据源。在若依中这个标识是通过DataSource注解来指定的。这种设计有三大优势解耦性强业务代码无需关心具体连接哪个数据库扩展灵活新增数据源只需添加配置不修改核心逻辑性能可控每个数据源可以独立配置连接池参数我建议的架构方案是MySQL作为主库处理核心业务用户、权限、订单等PostgreSQL作为从库支撑GIS空间数据和报表分析。这种组合既保证了事务一致性又发挥了PostgreSQL在复杂查询方面的优势。2. 环境准备与依赖配置开始配置前需要确保开发环境已经准备好。我这里以若依4.7.5版本为例演示完整配置过程。必备组件清单JDK 1.8Maven 3.6MySQL 5.7PostgreSQL 12Redis用于会话管理首先在ruoyi-admin的pom.xml中添加PostgreSQL驱动依赖dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId version42.3.1/version /dependency注意版本兼容性我测试过42.2.x和42.3.x系列都可以正常工作。如果使用较新的PostgreSQL 14建议使用42.3.x驱动。对于Druid连接池若依已经内置支持无需额外配置。但建议检查druid-spring-boot-starter版本最好保持在1.2.8以上dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.8/version /dependency3. 数据源详细配置修改application-druid.yml配置文件这是配置的关键步骤。我建议采用以下结构spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主数据源MySQL master: url: jdbc:mysql://localhost:3306/ry?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 从数据源PostgreSQL slave: enabled: true # 启用从数据源 url: jdbc:postgresql://localhost:5432/ry_pg?stringtypeunspecified username: postgres password: postgres # driver-class-name: org.postgresql.Driver # 注释掉让Druid自动检测 initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000特别注意PostgreSQL的url参数stringtypeunspecified这个参数可以解决类型转换问题。我在实际项目中遇到过没有这个参数导致Hstore类型处理异常的情况。对于Druid连接池的调优参数根据我的经验initial-size建议与min-idle保持一致max-active根据服务器配置和并发量调整一般20-50之间max-wait设置为60000ms(1分钟)比较合理4. 动态数据源实现若依已经内置了多数据源支持我们只需要扩展即可。首先在DataSourceType枚举中添加新类型public enum DataSourceType { /** * 主库 */ MASTER, /** * 从库 */ SLAVE, /** * PostgreSQL库 */ POSTGRESQL }然后在DruidConfig类中添加数据源BeanBean ConfigurationProperties(spring.datasource.druid.postgresql) public DataSource postgresqlDataSource(DruidProperties druidProperties) { DruidDataSource dataSource druidProperties.createDataSource(); // 特殊配置示例设置PostgreSQL专属连接属性 dataSource.setConnectionInitSqls(Arrays.asList(SET search_path TO public)); return dataSource; }动态数据源配置的关键在于targetDataSources的设置Primary Bean(name dynamicDataSource) public DynamicDataSource dynamicDataSource( Qualifier(masterDataSource) DataSource masterDataSource, Qualifier(slaveDataSource) DataSource slaveDataSource, Qualifier(postgresqlDataSource) DataSource postgresqlDataSource) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource); targetDataSources.put(DataSourceType.POSTGRESQL.name(), postgresqlDataSource); // 设置默认数据源 return new DynamicDataSource(masterDataSource, targetDataSources); }5. PostgreSQL特有配置由于PostgreSQL与MySQL存在语法差异需要进行一些适配工作。以下是我总结的常见问题解决方案1. 分页插件配置在application.yml中修改PageHelper配置pagehelper: helper-dialect: postgresql reasonable: true support-methods-arguments: true2. SQL函数替换在mapper.xml文件中需要替换以下函数sysdate()→now()ifnull(a, b)→coalesce(a, b)database()→current_database()3. 序列处理PostgreSQL没有自增列需要用序列替代CREATE SEQUENCE seq_table_id START WITH 1 INCREMENT BY 1; ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval(seq_table_id);4. 类型映射在JDBC URL中添加参数解决类型问题jdbc:postgresql://host:port/db?stringtypeunspecifiedbinaryTransfertrue6. 事务管理方案跨库事务是分布式系统中的难点若依默认使用Spring的Transactional注解。对于多数据源场景我有以下建议方案一分布式事务适合强一致性要求// 使用Seata实现分布式事务 GlobalTransactional public void crossDatabaseOperation() { // 操作MySQL mysqlMapper.update(); // 操作PostgreSQL postgresqlMapper.insert(); }方案二最终一致性推荐public void eventualConsistencyOperation() { // 第一阶段主库操作 mysqlMapper.update(); try { // 第二阶段从库操作 postgresqlMapper.insert(); } catch (Exception e) { // 记录补偿日志 compensationService.recordFailure(); } }方案三链式事务简单场景Transactional(transactionManager masterTransactionManager) public void chainedOperation() { mysqlMapper.update(); try { DataSourceHolder.set(DataSourceType.POSTGRESQL); postgresqlMapper.insert(); } finally { DataSourceHolder.clear(); } }在实际项目中我更多采用方案二通过消息队列实现最终一致性。比如使用RocketMQ的事务消息public void withMQTransaction() { // 1. 准备消息 Message msg buildMessage(); // 2. 执行本地事务 transactionTemplate.execute(status - { mysqlMapper.update(); return Boolean.TRUE; }); // 3. 发送消息 rocketMQTemplate.sendMessageInTransaction(tx-group, topic, msg, null); }7. 实战案例GIS模块集成下面通过一个真实案例展示如何使用PostgreSQL数据源。假设我们需要在物流系统中实现地理围栏功能1. 创建PostGIS扩展CREATE EXTENSION postgis; CREATE EXTENSION hstore;2. 地理围栏表设计CREATE TABLE geo_fences ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, area GEOMETRY(POLYGON, 4326), properties HSTORE, create_time TIMESTAMP DEFAULT now() );3. Mapper接口DataSource(DataSourceType.POSTGRESQL) public interface GeoFenceMapper { Select(SELECT id, name, ST_AsText(area) as wkt FROM geo_fences WHERE ST_Contains(area, ST_Point(#{lng}, #{lat}))) ListGeoFence findFencesContainingPoint(Param(lng) double longitude, Param(lat) double latitude); }4. 服务层实现Service public class GeoFenceService { Autowired private GeoFenceMapper geoFenceMapper; DataSource(DataSourceType.POSTGRESQL) public boolean isInFence(double lng, double lat) { return !geoFenceMapper.findFencesContainingPoint(lng, lat).isEmpty(); } Transactional(transactionManager postgresqlTransactionManager) public void addFence(String name, String wktPolygon) { geoFenceMapper.insertFence(name, ST_GeomFromText(#{wkt}), wktPolygon); } }5. 控制器示例RestController RequestMapping(/geo) public class GeoController { Autowired private GeoFenceService fenceService; GetMapping(/check) public AjaxResult checkPosition(RequestParam double lng, RequestParam double lat) { boolean inside fenceService.isInFence(lng, lat); return AjaxResult.success(inside ? 在围栏内 : 在围栏外); } }这个案例展示了如何利用PostgreSQL的PostGIS扩展处理空间数据同时与主MySQL数据源协同工作。我在实际项目中采用这种架构后地理查询性能提升了5倍以上。8. 性能优化建议经过多个项目的实践我总结出以下优化经验连接池调优参数spring: datasource: druid: postgresql: # 连接池配置 initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000 # 监控配置 filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/* web-stat-filter: enabled: true url-pattern: /* exclusions: *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*PostgreSQL专属优化调整work_mem参数SET work_mem 16MB为常用查询字段创建GIN索引CREATE INDEX idx_geo_fence_area ON geo_fences USING GIST(area);定期执行VACUUM ANALYZE维护混合查询方案 对于需要跨库查询的场景我推荐以下模式public ListMixedResult getMixedData(Long userId) { // 从MySQL获取用户基础信息 User user userMapper.selectById(userId); // 从PostgreSQL获取地理信息 ListGeoInfo geoInfos geoInfoMapper.findByUserId(userId); // 内存中组合结果 return geoInfos.stream() .map(geo - new MixedResult(user, geo)) .collect(Collectors.toList()); }9. 常见问题排查在集成过程中我遇到过不少坑这里分享几个典型问题的解决方法问题1PostgreSQL驱动类找不到java.lang.ClassNotFoundException: org.postgresql.Driver解决方案检查依赖是否引入推荐使用42.x系列驱动问题2类型转换异常org.postgresql.util.PSQLException: 错误: 字段xxx的类型为integer但表达式的类型为character varying解决方案在JDBC URL中添加stringtypeunspecified参数问题3事务不生效Transaction not active解决方案确保为PostgreSQL配置了独立的事务管理器Bean public PlatformTransactionManager postgresqlTransactionManager( Qualifier(postgresqlDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }问题4分页查询错误解决方案确保PageHelper方言设置为postgresqlpagehelper: helper-dialect: postgresql问题5序列不存在ERROR: relation seq_table_id does not exist解决方案创建序列并设置默认值CREATE SEQUENCE seq_table_id START 1; ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval(seq_table_id);10. 最佳实践总结经过多个项目的实战检验我总结出以下最佳实践明确数据源分工MySQL适合事务密集型操作PostgreSQL适合复杂查询和特殊数据类型合理使用注解在Service层使用DataSource避免在Mapper层过度使用统一异常处理自定义异常处理器处理多数据源特有的异常监控配置为每个数据源配置独立的监控和告警文档规范在项目文档中明确记录各数据源的用途和配置参数一个典型的项目结构建议src/main/java ├── com.ruoyi │ ├── common │ │ └── datasource │ │ ├── annotation │ │ │ └── DataSource.java │ │ ├── config │ │ │ └── DruidConfig.java │ │ └── enums │ │ └── DataSourceType.java │ ├── modules │ │ ├── mysql │ │ │ └── service │ │ └── postgresql │ │ ├── mapper │ │ └── service │ └── system └── resources ├── mapper │ ├── mysql │ └── postgresql └── application-druid.yml在最近的一个电商平台项目中我们采用这种架构后订单核心模块的TPS提升了30%地理位置查询响应时间从200ms降到50ms报表生成时间从分钟级缩短到秒级
若依框架多数据源配置:MySQL与PostgreSQL双库并存实践
发布时间:2026/7/15 2:00:33
1. 多数据源架构设计思路在实际企业级应用中混合使用多种数据库已经成为常态。比如核心业务数据使用MySQL存储而地理信息系统(GIS)或分析模块则更适合PostgreSQL。若依框架通过动态数据源机制完美支持这种混合架构我最近在一个物流管理系统中就采用了这种方案。多数据源的核心原理是通过AbstractRoutingDataSource实现动态路由。简单来说它就像一个智能路由器根据我们设置的标识来决定使用哪个数据源。在若依中这个标识是通过DataSource注解来指定的。这种设计有三大优势解耦性强业务代码无需关心具体连接哪个数据库扩展灵活新增数据源只需添加配置不修改核心逻辑性能可控每个数据源可以独立配置连接池参数我建议的架构方案是MySQL作为主库处理核心业务用户、权限、订单等PostgreSQL作为从库支撑GIS空间数据和报表分析。这种组合既保证了事务一致性又发挥了PostgreSQL在复杂查询方面的优势。2. 环境准备与依赖配置开始配置前需要确保开发环境已经准备好。我这里以若依4.7.5版本为例演示完整配置过程。必备组件清单JDK 1.8Maven 3.6MySQL 5.7PostgreSQL 12Redis用于会话管理首先在ruoyi-admin的pom.xml中添加PostgreSQL驱动依赖dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId version42.3.1/version /dependency注意版本兼容性我测试过42.2.x和42.3.x系列都可以正常工作。如果使用较新的PostgreSQL 14建议使用42.3.x驱动。对于Druid连接池若依已经内置支持无需额外配置。但建议检查druid-spring-boot-starter版本最好保持在1.2.8以上dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.8/version /dependency3. 数据源详细配置修改application-druid.yml配置文件这是配置的关键步骤。我建议采用以下结构spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主数据源MySQL master: url: jdbc:mysql://localhost:3306/ry?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 从数据源PostgreSQL slave: enabled: true # 启用从数据源 url: jdbc:postgresql://localhost:5432/ry_pg?stringtypeunspecified username: postgres password: postgres # driver-class-name: org.postgresql.Driver # 注释掉让Druid自动检测 initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000特别注意PostgreSQL的url参数stringtypeunspecified这个参数可以解决类型转换问题。我在实际项目中遇到过没有这个参数导致Hstore类型处理异常的情况。对于Druid连接池的调优参数根据我的经验initial-size建议与min-idle保持一致max-active根据服务器配置和并发量调整一般20-50之间max-wait设置为60000ms(1分钟)比较合理4. 动态数据源实现若依已经内置了多数据源支持我们只需要扩展即可。首先在DataSourceType枚举中添加新类型public enum DataSourceType { /** * 主库 */ MASTER, /** * 从库 */ SLAVE, /** * PostgreSQL库 */ POSTGRESQL }然后在DruidConfig类中添加数据源BeanBean ConfigurationProperties(spring.datasource.druid.postgresql) public DataSource postgresqlDataSource(DruidProperties druidProperties) { DruidDataSource dataSource druidProperties.createDataSource(); // 特殊配置示例设置PostgreSQL专属连接属性 dataSource.setConnectionInitSqls(Arrays.asList(SET search_path TO public)); return dataSource; }动态数据源配置的关键在于targetDataSources的设置Primary Bean(name dynamicDataSource) public DynamicDataSource dynamicDataSource( Qualifier(masterDataSource) DataSource masterDataSource, Qualifier(slaveDataSource) DataSource slaveDataSource, Qualifier(postgresqlDataSource) DataSource postgresqlDataSource) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource); targetDataSources.put(DataSourceType.POSTGRESQL.name(), postgresqlDataSource); // 设置默认数据源 return new DynamicDataSource(masterDataSource, targetDataSources); }5. PostgreSQL特有配置由于PostgreSQL与MySQL存在语法差异需要进行一些适配工作。以下是我总结的常见问题解决方案1. 分页插件配置在application.yml中修改PageHelper配置pagehelper: helper-dialect: postgresql reasonable: true support-methods-arguments: true2. SQL函数替换在mapper.xml文件中需要替换以下函数sysdate()→now()ifnull(a, b)→coalesce(a, b)database()→current_database()3. 序列处理PostgreSQL没有自增列需要用序列替代CREATE SEQUENCE seq_table_id START WITH 1 INCREMENT BY 1; ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval(seq_table_id);4. 类型映射在JDBC URL中添加参数解决类型问题jdbc:postgresql://host:port/db?stringtypeunspecifiedbinaryTransfertrue6. 事务管理方案跨库事务是分布式系统中的难点若依默认使用Spring的Transactional注解。对于多数据源场景我有以下建议方案一分布式事务适合强一致性要求// 使用Seata实现分布式事务 GlobalTransactional public void crossDatabaseOperation() { // 操作MySQL mysqlMapper.update(); // 操作PostgreSQL postgresqlMapper.insert(); }方案二最终一致性推荐public void eventualConsistencyOperation() { // 第一阶段主库操作 mysqlMapper.update(); try { // 第二阶段从库操作 postgresqlMapper.insert(); } catch (Exception e) { // 记录补偿日志 compensationService.recordFailure(); } }方案三链式事务简单场景Transactional(transactionManager masterTransactionManager) public void chainedOperation() { mysqlMapper.update(); try { DataSourceHolder.set(DataSourceType.POSTGRESQL); postgresqlMapper.insert(); } finally { DataSourceHolder.clear(); } }在实际项目中我更多采用方案二通过消息队列实现最终一致性。比如使用RocketMQ的事务消息public void withMQTransaction() { // 1. 准备消息 Message msg buildMessage(); // 2. 执行本地事务 transactionTemplate.execute(status - { mysqlMapper.update(); return Boolean.TRUE; }); // 3. 发送消息 rocketMQTemplate.sendMessageInTransaction(tx-group, topic, msg, null); }7. 实战案例GIS模块集成下面通过一个真实案例展示如何使用PostgreSQL数据源。假设我们需要在物流系统中实现地理围栏功能1. 创建PostGIS扩展CREATE EXTENSION postgis; CREATE EXTENSION hstore;2. 地理围栏表设计CREATE TABLE geo_fences ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, area GEOMETRY(POLYGON, 4326), properties HSTORE, create_time TIMESTAMP DEFAULT now() );3. Mapper接口DataSource(DataSourceType.POSTGRESQL) public interface GeoFenceMapper { Select(SELECT id, name, ST_AsText(area) as wkt FROM geo_fences WHERE ST_Contains(area, ST_Point(#{lng}, #{lat}))) ListGeoFence findFencesContainingPoint(Param(lng) double longitude, Param(lat) double latitude); }4. 服务层实现Service public class GeoFenceService { Autowired private GeoFenceMapper geoFenceMapper; DataSource(DataSourceType.POSTGRESQL) public boolean isInFence(double lng, double lat) { return !geoFenceMapper.findFencesContainingPoint(lng, lat).isEmpty(); } Transactional(transactionManager postgresqlTransactionManager) public void addFence(String name, String wktPolygon) { geoFenceMapper.insertFence(name, ST_GeomFromText(#{wkt}), wktPolygon); } }5. 控制器示例RestController RequestMapping(/geo) public class GeoController { Autowired private GeoFenceService fenceService; GetMapping(/check) public AjaxResult checkPosition(RequestParam double lng, RequestParam double lat) { boolean inside fenceService.isInFence(lng, lat); return AjaxResult.success(inside ? 在围栏内 : 在围栏外); } }这个案例展示了如何利用PostgreSQL的PostGIS扩展处理空间数据同时与主MySQL数据源协同工作。我在实际项目中采用这种架构后地理查询性能提升了5倍以上。8. 性能优化建议经过多个项目的实践我总结出以下优化经验连接池调优参数spring: datasource: druid: postgresql: # 连接池配置 initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000 # 监控配置 filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/* web-stat-filter: enabled: true url-pattern: /* exclusions: *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*PostgreSQL专属优化调整work_mem参数SET work_mem 16MB为常用查询字段创建GIN索引CREATE INDEX idx_geo_fence_area ON geo_fences USING GIST(area);定期执行VACUUM ANALYZE维护混合查询方案 对于需要跨库查询的场景我推荐以下模式public ListMixedResult getMixedData(Long userId) { // 从MySQL获取用户基础信息 User user userMapper.selectById(userId); // 从PostgreSQL获取地理信息 ListGeoInfo geoInfos geoInfoMapper.findByUserId(userId); // 内存中组合结果 return geoInfos.stream() .map(geo - new MixedResult(user, geo)) .collect(Collectors.toList()); }9. 常见问题排查在集成过程中我遇到过不少坑这里分享几个典型问题的解决方法问题1PostgreSQL驱动类找不到java.lang.ClassNotFoundException: org.postgresql.Driver解决方案检查依赖是否引入推荐使用42.x系列驱动问题2类型转换异常org.postgresql.util.PSQLException: 错误: 字段xxx的类型为integer但表达式的类型为character varying解决方案在JDBC URL中添加stringtypeunspecified参数问题3事务不生效Transaction not active解决方案确保为PostgreSQL配置了独立的事务管理器Bean public PlatformTransactionManager postgresqlTransactionManager( Qualifier(postgresqlDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }问题4分页查询错误解决方案确保PageHelper方言设置为postgresqlpagehelper: helper-dialect: postgresql问题5序列不存在ERROR: relation seq_table_id does not exist解决方案创建序列并设置默认值CREATE SEQUENCE seq_table_id START 1; ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval(seq_table_id);10. 最佳实践总结经过多个项目的实战检验我总结出以下最佳实践明确数据源分工MySQL适合事务密集型操作PostgreSQL适合复杂查询和特殊数据类型合理使用注解在Service层使用DataSource避免在Mapper层过度使用统一异常处理自定义异常处理器处理多数据源特有的异常监控配置为每个数据源配置独立的监控和告警文档规范在项目文档中明确记录各数据源的用途和配置参数一个典型的项目结构建议src/main/java ├── com.ruoyi │ ├── common │ │ └── datasource │ │ ├── annotation │ │ │ └── DataSource.java │ │ ├── config │ │ │ └── DruidConfig.java │ │ └── enums │ │ └── DataSourceType.java │ ├── modules │ │ ├── mysql │ │ │ └── service │ │ └── postgresql │ │ ├── mapper │ │ └── service │ └── system └── resources ├── mapper │ ├── mysql │ └── postgresql └── application-druid.yml在最近的一个电商平台项目中我们采用这种架构后订单核心模块的TPS提升了30%地理位置查询响应时间从200ms降到50ms报表生成时间从分钟级缩短到秒级