1. 多数据源架构设计思路
在实际企业级应用中,混合使用多种数据库已经成为常态。比如核心业务数据使用MySQL存储,而地理信息系统(GIS)或分析模块则更适合PostgreSQL。若依框架通过动态数据源机制完美支持这种混合架构,我最近在一个物流管理系统中就采用了这种方案。
多数据源的核心原理是通过AbstractRoutingDataSource实现动态路由。简单来说,它就像一个智能路由器,根据我们设置的标识来决定使用哪个数据源。在若依中,这个标识是通过@DataSource注解来指定的。这种设计有三大优势:
- 解耦性强:业务代码无需关心具体连接哪个数据库
- 扩展灵活:新增数据源只需添加配置,不修改核心逻辑
- 性能可控:每个数据源可以独立配置连接池参数
我建议的架构方案是:MySQL作为主库处理核心业务(用户、权限、订单等),PostgreSQL作为从库支撑GIS空间数据和报表分析。这种组合既保证了事务一致性,又发挥了PostgreSQL在复杂查询方面的优势。
2. 环境准备与依赖配置
开始配置前,需要确保开发环境已经准备好。我这里以若依4.7.5版本为例,演示完整配置过程。
必备组件清单:
- JDK 1.8+
- Maven 3.6+
- MySQL 5.7+
- PostgreSQL 12+
- Redis(用于会话管理)
首先在ruoyi-admin的pom.xml中添加PostgreSQL驱动依赖:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.3.1</version> </dependency>注意版本兼容性,我测试过42.2.x和42.3.x系列都可以正常工作。如果使用较新的PostgreSQL 14+,建议使用42.3.x驱动。
对于Druid连接池,若依已经内置支持,无需额外配置。但建议检查druid-spring-boot-starter版本,最好保持在1.2.8以上:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency>3. 数据源详细配置
修改application-druid.yml配置文件,这是配置的关键步骤。我建议采用以下结构:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主数据源(MySQL) master: url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 从数据源(PostgreSQL) slave: enabled: true # 启用从数据源 url: jdbc:postgresql://localhost:5432/ry_pg?stringtype=unspecified 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参数stringtype=unspecified,这个参数可以解决类型转换问题。我在实际项目中遇到过没有这个参数导致Hstore类型处理异常的情况。
对于Druid连接池的调优参数,根据我的经验:
- initial-size建议与min-idle保持一致
- max-active根据服务器配置和并发量调整,一般20-50之间
- max-wait设置为60000ms(1分钟)比较合理
4. 动态数据源实现
若依已经内置了多数据源支持,我们只需要扩展即可。首先在DataSourceType枚举中添加新类型:
public enum DataSourceType { /** * 主库 */ MASTER, /** * 从库 */ SLAVE, /** * PostgreSQL库 */ POSTGRESQL }然后在DruidConfig类中添加数据源Bean:
@Bean @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) { Map<Object, 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?stringtype=unspecified&binaryTransfer=true6. 事务管理方案
跨库事务是分布式系统中的难点,若依默认使用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}))") List<GeoFence> 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 List<MixedResult> getMixedData(Long userId) { // 从MySQL获取用户基础信息 User user = userMapper.selectById(userId); // 从PostgreSQL获取地理信息 List<GeoInfo> geoInfos = geoInfoMapper.findByUserId(userId); // 内存中组合结果 return geoInfos.stream() .map(geo -> new MixedResult(user, geo)) .collect(Collectors.toList()); }9. 常见问题排查
在集成过程中,我遇到过不少坑,这里分享几个典型问题的解决方法:
问题1:PostgreSQL驱动类找不到
java.lang.ClassNotFoundException: org.postgresql.Driver解决方案:检查依赖是否引入,推荐使用42.x系列驱动
问题2:类型转换异常
org.postgresql.util.PSQLException: 错误: 字段"xxx"的类型为integer,但表达式的类型为character varying解决方案:在JDBC URL中添加stringtype=unspecified参数
问题3:事务不生效
Transaction not active解决方案:确保为PostgreSQL配置了独立的事务管理器
@Bean public PlatformTransactionManager postgresqlTransactionManager( @Qualifier("postgresqlDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }问题4:分页查询错误解决方案:确保PageHelper方言设置为postgresql
pagehelper: 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
- 报表生成时间从分钟级缩短到秒级