若依框架如何配置多数据源?同时连接 MySQL、SQL Server、Firebird 三种数据库

一套系统连三种数据库,不是炫技,是制造业数字化的真实刚需。这篇文章复盘我在若依框架上踩过的多数据源深坑------从事务不回滚到动态切换翻车,每一个坑都花了至少一天。


一、为什么一个系统要连三种数据库?

先交代背景。我们的 MES 系统要跑在印刷包装厂的环境里,这些工厂的 IT 现状通常是这样的:

数据库 用途 为什么不能动
MySQL 8 我们自己的 MES 主库 新建的,可控
SQL Server 客户的旧 ERP 系统 用了 8 年,不敢动
Firebird 老旧的印刷行业软件 用得更久,更不敢动

老板的要求很简单:新系统不能要求客户换掉旧系统。数据必须在三套库之间流转------工单从 ERP 同步过来、生产数据写进 MES、部分统计还要回写到 Firebird 给老软件读。

这就叫异构数据库数据同步,听起来高大上,做起来全是坑。

注:本文基于若依框架(RuoYi Spring Boot 3 版本)的多数据源方案进行实战讲解。方案思路可复用到任何 Spring Boot 项目。


二、若依框架的多数据源原理

2.1 框架做了什么

若依框架内置了基于 dynamic-datasource 的多数据源支持。核心思路是注解切换

less 复制代码
@DS("master")    // 走主库
public void insertOrder() { ... }

@DS("erp")       // 走客户的 SQL Server
public List<Order> syncOrder() { ... }

框架帮你做的事情:

  • 通过 AOP 拦截 @DS 注解,自动切换数据源
  • 支持多数据源的事务管理
  • 配合 Druid 连接池

看起来很简单对吧?接下来就是实际场景中爆出来的坑。

2.2 框架没做的事

若依的多数据源方案有个前提假设:所有库的表结构是你可控的。但实际情况是:

  • SQL Server 里的表是别人建的,字段命名毫无规律
  • Firebird 的表连文档都没有,全靠猜
  • 三套库的数据类型不完全对应

这些才是真正的难度所在。


三、第一步:配置多数据源

3.1 数据源配置

ruby 复制代码
# application-druid.yml
spring:
  datasource:
    druid:
      # 主数据源 - MES 系统
      master:
        url: jdbc:mysql://localhost:3306/your_mes_db?useUnicode=true&characterEncoding=utf8
        username: your_username
        password: ${MYSQL_PWD}
        driver-class-name: com.mysql.cj.jdbc.Driver

      # 从数据源 - 客户 ERP(SQL Server)
      erp:
        url: jdbc:sqlserver://HOST:1433;DatabaseName=LEGACY_ERP
        username: your_username
        password: ${SQLSERVER_PWD}
        driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver

      # 从数据源 - 老旧印刷软件(Firebird)
      legacy:
        url: jdbc:firebirdsql://HOST:3050/PATH/TO/LEGACY.FDB?encoding=GB2312
        username: your_username
        password: ${FIREBIRD_PWD}
        driver-class-name: org.firebirdsql.jdbc.FBDriver

3.2 引入驱动依赖

xml 复制代码
<!-- pom.xml -->
<!-- MySQL 驱动(若依已自带) -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<!-- SQL Server 驱动 -->
<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>12.6.1.jre11</version>
</dependency>

<!-- Firebird 驱动 -->
<dependency>
    <groupId>org.firebirdsql.jdbc</groupId>
    <artifactId>jaybird</artifactId>
    <version>5.0.4.java11</version>
</dependency>

⚠️ 坑 #1:Firebird 驱动版本混乱

Jaybird 4.x 需要 Java 11+,但有些老版本 Firebird 数据库(2.x)只能用 Jaybird 3.x 连接。先确认对方 Firebird 的版本,否则连上去就是 GDS Exception


四、真正的大坑:多数据源事务管理

4.1 切换数据源后事务不回滚

这是最常见也最致命的问题:

typescript 复制代码
// ❌ 错误写法:切换数据源后,事务管理会乱
@Service
public class OrderSyncService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DS("master")
    @Transactional
    public void syncOrderFromErp(Long orderId) {
        // 第一步:从 SQL Server 读取工单数据
        String sql = "SELECT * FROM TB_ORDER WHERE ID = ?";
        Map<String, Object> order = jdbcTemplate.queryForMap(sql, orderId);

        // 第二步:写入 MySQL(此时数据源已经切回 master)
        jdbcTemplate.update("INSERT INTO mes_order (...) VALUES (...)", ...);

        // 第三步:如果这里抛异常------
        throw new RuntimeException("模拟异常");
        // MySQL 的 INSERT 已经提交了!不会回滚!
    }
}

原因 :Spring 的 @Transactional 默认绑定的是 masterTransactionManager。当你在 erp 数据源上执行查询后,erp 的事务已经独立提交,和 master 事务不在同一个事务管理器下。

4.2 正确做法:显式指定事务管理器 + 补偿机制

typescript 复制代码
// ✅ 正确写法
@Service
public class OrderSyncService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DS("erp")
    public Map<String, Object> readOrderFromErp(Long orderId) {
        return jdbcTemplate.queryForMap(
            "SELECT * FROM TB_ORDER WHERE ID = ?", orderId
        );
    }

    @DS("master")
    @Transactional(transactionManager = "masterTransactionManager")
    public void saveOrderToMes(Map<String, Object> orderData) {
        jdbcTemplate.update("INSERT INTO mes_order (...) VALUES (...)", ...);
    }

    // 协调方法:逐条同步,加补偿逻辑
    public SyncResult syncOneOrder(Long orderId) {
        try {
            Map<String, Object> data = readOrderFromErp(orderId);
            saveOrderToMes(data);
            return SyncResult.success(orderId);
        } catch (Exception e) {
            log.error("工单 {} 同步失败", orderId, e);
            return SyncResult.fail(orderId, e.getMessage());
        }
    }
}

核心原则 :跨数据库的强一致性基本做不到。工程上的做法是 逐条同步 + 异常补偿 + 定时重试


五、第二个大坑:异构数据库的数据类型映射

三个数据库的数据类型各有各的脾气:

数据类型 MySQL SQL Server Firebird 处理方式
日期时间 datetime datetime TIMESTAMP 统一转 LocalDateTime
布尔 tinyint(1) bit SMALLINT 用 Integer 接收,代码层判断
长文本 longtext nvarchar(max) BLOB SUB_TYPE TEXT 统一用 String 接收
金额 decimal(18,2) money NUMERIC(18,2) BigDecimal 统一处理

SQL Server 的 money 类型映射到 Java 时,默认会变成 Double,导致精度丢失。一个客户的工单金额 12345.67 同步后变成了 12345.669999...

ini 复制代码
// ❌ 错误:精度丢失
Double amount = (Double) row.get("TOTAL_AMOUNT");

// ✅ 正确:强制用 BigDecimal
BigDecimal amount = (BigDecimal) row.get("TOTAL_AMOUNT");

六、第三个坑:Firebird 的编码问题

arduino 复制代码
// ❌ 不指定编码,中文全部乱码
"jdbc:firebirdsql://HOST:3050/PATH/TO/LEGACY.FDB"

// ✅ 必须指定 encoding
"jdbc:firebirdsql://HOST:3050/PATH/TO/LEGACY.FDB?encoding=GB2312"

如果不知道对方建库时的字符集,用 Firebird 的 gstat 工具查:

perl 复制代码
gstat -h YOUR_DB.GDB | grep "Character set"

拿不到的话,就用 GB2312 和 UTF8 各试一次,看哪次不出现"锟斤拷"。


七、增量同步策略

全量同步不现实。增量同步的关键是找到可靠的变更标识

条件 方案
UPDATE_TIME 用时间戳做增量标记
有自增 ID MAX(id) 做增量
CREATE_DATE 按日期分区,每天同步当日数据
什么都没有 全量查询 + 本地 MD5 比对

如果对方允许,帮他们在 ERP 表上加一个 UPDATE_TIME 字段 + 触发器自动更新,比任何取巧方案都靠谱。


八、踩坑汇总

原因 解决
多数据源事务不回滚 TransactionManager 显式指定,读写分离
跨库强一致性 数据库物理隔离 逐条同步 + 补偿重试
SQL Server money 精度丢失 JDBC 映射为 Double 强制用 BigDecimal
Firebird 中文乱码 字符集不匹配 URL 指定 encoding
异构表字段映射 三套库各自为政 建 DTO 层做转换
驱动版本不兼容 数据库版本和 JDBC 驱动不对 先确认版本再选驱动

九、总结

多数据源这件事,配通只需要半小时,跑稳需要半个月。核心经验:

  1. 别幻想分布式事务:逐条同步 + 补偿机制是工程上最务实的方案
  2. 提前确认数据库版本和字符集:尤其是 Firebird 这种小众数据库
  3. 建一层 DTO 映射:不要把异构库字段直接暴露给业务代码
  4. 增量同步是必须的:全量同步撑不过第一个月

📌 关于作者

我是一名全栈开发者,目前在深圳创业,专注于印刷包装行业的数字化系统建设。

技术栈:Java / Spring Boot / Vue3 / uni-app / MySQL / Redis

我会持续分享全栈开发实战、若依框架深度教程、MES & CRM 产品设计思路。

如果这篇文章帮你省下了踩坑的时间,点个赞 👍 让我知道。更多实战内容,欢迎关注微信公众号「MqCode」,每周更新。

相关推荐
协享科技1 小时前
Spring Boot 与 Go 双服务架构实践:从单体拆分到通信设计
java·人工智能·spring boot·后端·架构·golang·ai编程
柒和远方1 小时前
后端认证、鉴权、高并发:从 Session 到 JWT 再到 Redis
前端·后端·面试
dearxue2 小时前
这一次,我们一起把AI的复杂一口吃掉
人工智能·后端
打字机v2 小时前
OOP 面向对象 java 基础--服务+maven+mysql
后端
fliter2 小时前
Rust 项目管理动态 — 2026 年 2 月
后端
苍何2 小时前
一个令人惊艳的开源项目,Agent Skill 开始自进化了?
后端
小研说技术2 小时前
Spring AI实现rag流程(简易版)
java·后端
Nturmoils3 小时前
自增主键别只会 auto_increment,先把值从哪来讲清楚
数据库·后端
Slice_cy3 小时前
基于node实现服务端内核引擎
前端·后端