恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
若依微服务整合MySQL与达梦数据库的多数据源实战改造
首页
资讯中心
/
若依微服务整合MySQL与达梦数据库的多数据源实战改造
若依微服务整合MySQL与达梦数据库的多数据源实战改造
发布时间:2026/10/9 2:48:01
若依微服务项目里同时要让MySQL和达梦DM数据库协同工作这种多数据源需求这两年越来越常见。简单说就是在同一个若依微服务模块中既能查MySQL里的存量业务数据也能查达梦数据库里的核心统计表两边互不干扰。这篇文章就是一次真实改造的全过程记录从方案选型到Nacos配置再到切换原理和踩坑适合正在做国产化适配或者需要在若依里接多个数据库的Java后端同学参考。我这次接手的是一个已经跑了一年多的若依微服务项目原来所有数据都放在MySQL里。后来因为国产化改造部分核心数据要求落到达梦数据库但老业务又不可能立刻迁移干净于是最现实的办法就是让服务同时连两套库。当时第一反应是“不就是加个数据源嘛”真上手才发现若依自己那套Druid、MyBatis、事务管理放在那里配置、切换、SQL方言的坑一个都不少。折腾了两三天最终用dynamic-datasource把事情落地了。下面把整个思路和过程完整写出来。1. 需求定位与多数据源方案选型1.1 若依微服务里为什么会有多数据源需求若依微服务版本RuoYi-Cloud本身是基于Spring Cloud Alibaba构建的典型结构是Gateway网关做统一入口Nacos做注册中心和配置中心下面挂着ruoyi-auth、ruoyi-system、ruoyi-file、ruoyi-job等一堆服务模块。正常情况下每个微服务只连接自己对应的那一个数据库配置也统一托管在Nacos上Spring Boot自动装配一套数据源就够了。但实际业务一跑起来单数据源就不够用了。最常见的场景就是我遇到的这种系统早期所有数据都在MySQL里后来由于国产化要求某些新模块的数据必须落在达梦数据库同时旧数据又不能马上迁移。这时候同一个业务服务里就有必要同时维护MySQL和达梦两套连接。除了这种信创改造场景多数据源在若依微服务里还有其他典型用途。比如读写分离主库负责写、从库负责读比如不同业务域的数据隔离订单库和报表库分开再比如历史数据归档把一年前的数据放到单独的库里冷热分离。换句话说多数据源不是为配置而配置它背后一定有一个明确的数据归属边界要解决。有一点需要先强调如果两个数据源之间没有强事务关联拆分服务比强行多数据源更干净。但现实里往往因为报表汇总、关联查询这类需求你不得不让服务直连两套库。这时候多数据源就是最务实的解法。1.2 多数据源实现方案如何选Java生态里做多数据源绕不开三个路子。第一个是Spring原生方案自己定义多个DataSource再分别为每个数据源配置独立的SqlSessionFactory、PlatformTransactionManager和Mapper扫描路径。这套方案能用但配置量非常大每加一个数据源要动一堆Bean定义而且事务管理很容易记住当前线程到底用的哪个数据源。在若依这种本身已经封装了Druid和MyBatis的框架里手动配置搞到最后就是一句话能跑但谁维护谁难受。第二个是使用baomidou的dynamic-datasource-spring-boot-starter。它是MyBatis-Plus团队出品的动态数据源组件核心思路是包装一个路由数据源通过DS注解在方法或类级别切换目标数据源底层用ThreadLocal保存数据源key。配置直观、文档完善、社区活跃而且在Druid连接池那一套下兼容得不错。这也是我最终选定的方案。第三个是自己写AOP切面去拦截方法、动态设置数据源key。技术上不复杂但属于重复造轮子。切换逻辑、嵌套切换、事务隔离、异常清理这些边界条件非常琐碎自己写的可靠性远不如成熟组件。选dynamic-datasource还有一个重要原因若依微服务本身已经重度使用Druid连接池和MyBatis家族dynamic-datasource对这些是开箱即用的。它的原理说白了就是把Spring原本的单DataSource替换成一个DynamicRoutingDataSource在每次拿数据库连接时根据ThreadLocal里存放的key决定真正路由到哪个DataSource。这个设计跟Spring的AbstractRoutingDataSource一脉相承理解成本很低。2. 环境准备与基础依赖引入2.1 先理清数据源配置应该放在哪个位置在若依微服务里做多数据源第一件事不是写pom依赖而是想清楚到底在哪个模块引入。若依微服务的所有服务配置都在Nacos上每个服务有自己独立的配置ID比如ruoyi-system-dev.yaml。任何一个服务的数据源配置本质上是写在自己的Nacos配置文件里。很多人一上来就在ruoyi-common-datasource公共模块里加dynamic-datasource依赖结果导致所有微服务全部被影响到这非常危险。不同服务的数据库本来就不同公共模块强行统一改数据源等于把每个服务的数据库路由都搅在一起了。我当时的做法是新建了一个独立的业务服务ruoyi-modules-report专门负责需要同时访问MySQL和达梦数据库的报表接口。这样做有几个好处影响面可控不会动到其他模块的数据源配置。业务边界清晰报表服务只做查询和统计不参与原有系统的主链路。后续如果性能有问题这个服务可以独立扩节点对整体架构没有影响。如果你不想新建服务而是要在现有服务里加多数据源原则也一样只改目标服务的Nacos配置公共依赖不要乱动。千万记住若依微服务里数据源的“作用域”是服务级别的不是全局的。2.2 引入多数据源依赖与国产数据库驱动依赖引入这一块其实有两个部分dynamic-datasource本身以及达梦数据库的JDBC驱动。dynamic-datasource的Maven坐标如下放在目标业务模块的pom.xml里即可dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version3.6.1/version /dependency版本方面我实际用的是3.6.1。如果你的若依版本比较旧或者Spring Boot版本不是2.x建议先查一下dynamic-datasource的版本兼容矩阵不要无脑上最新版。3.6.1这个版本对应Spring Boot 2.x生态配合若依微服务的Druid和MyBatis-Plus没有发现冲突。MySQL驱动不用额外操心若依的common模块里本身就有mysql-connector-java。但要注意MySQL服务端的版本如果数据库是MySQL 5.7尽量不要用8.0的驱动顶上去可能出现认证协议不匹配的问题。这个问题在接老库的时候特别容易踩驱动版本和数据库版本差太多时连接失败的报错还不太直观。达梦驱动则比较特殊。达梦官方并没有把JDBC驱动发布到Maven中央仓库常规做法是从达梦安装目录的drivers/jdbc里找jar包文件名一般类似DmJdbcDriver18.jar。拿到jar之后先手动安装到本地Maven仓库mvn install:install-file -DfileDmJdbcDriver18.jar -DgroupIdcom.dameng -DartifactIdDmJdbcDriver18 -Dversion8.1.2.192 -Dpackagingjar然后在pom.xml里按对应的groupId、artifactId、version引用。版本号不用照抄我这边的以你实际拿到的驱动文件版本为准。这里有一个很实在的提醒达梦驱动的包名是dm.jdbc.driver.DmDriver不是com.dameng开头的类名。后面配置driver-class-name的时候别写错了写错的话启动报错会有一堆初始化数据库连接失败的日志排查起来很闹心。3. 数据源配置与动态切换实现3.1 在Nacos里配置动态数据源参数当依赖都准备好之后重点就落到配置上。我这里以ruoyi-modules-report这个业务服务为例对应的Nacos配置文件是ruoyi-report-dev.yaml。在spring.datasource下增加dynamic配置spring: datasource: dynamic: primary: master strict: false datasource: master: url: jdbc:mysql://127.0.0.1:3306/ruoyi?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver dm: url: jdbc:dm://127.0.0.1:5236 username: SYSDBA password: SYSDBA driver-class-name: dm.jdbc.driver.DmDriver两个数据源的key分别是master和dmprimary: master表示默认走MySQL这个主库。这里我把主库定义为若依原来用的MySQL达梦库作为一个额外的切换目标。strict参数值得说清楚。strict: false的含义是如果代码里写了一个不存在的数据源key不会直接报错而是默默走主库。这在开发调试阶段很友好但到了生产环境就危险了——万一某个方法把key拼错了系统会毫无征兆地读到主库数据这个故障非常隐蔽。所以生产环境建议改成strict: true让非法key直接抛异常暴露问题。我项目里是上线前改成true的。另外dynamic-datasource支持每个数据源单独配置Druid连接池参数非常实用。达梦数据库和MySQL毕竟是两套东西连接池大小可以分开调。我当时给达梦库配置了相对保守的参数因为初期并发不大没必要让连接池空转参数MySQL主库达梦库说明initial-size52初始化连接数min-idle52最小空闲连接max-active2010最大活跃连接validation-querySELECT 1SELECT 1连接校验SQLtest-while-idletruetrue空闲连接检测time-between-eviction-runs-millis6000060000检测间隔补充一个细节达梦的默认端口是5236不是3306。驱动类名确是dm.jdbc.driver.DmDriver但URL前面的协议是jdbc:dm://如果你没有指定schema默认会连用户SYSDBA对应的默认库。3.2 基于DS注解实现数据源切换配置写好后代码层面最常用的就是DS注解。它的用法很直白标注在Service实现类或方法上value值填我们配置的数据源key比如DS(dm)就是切到达梦库。以报表服务为例我需要从MySQL里查用户基本信息再从达梦库里查电力设备的运行统计数据Service public class ReportServiceImpl implements ReportService { Override public ListUserInfoDTO listUsers() { // 不标注 DS默认走 primary 配置的 masterMySQL return userMapper.selectUserList(); } DS(dm) Override public ListDeviceStatDTO listDeviceStats() { // 明确切换到达梦库 return deviceStatMapper.selectDeviceStatList(); } }Mapper层什么都没有特殊处理XML照样写MyBatis照样按接口扫描。当你调用带DS(dm)的方法时dynamic-datasource会自动在进入方法时把数据源key设为dm执行完后再清理掉。整个过程对Mapper是透明的。还有个更省事的写法如果某个Service里的所有方法都只查达梦库可以直接在类上标注DS(dm)这样类下面所有方法默认都走达梦。方法上标注的优先级高于类上标注的也就是说遇到特殊情况可以在类级别达梦的基础上单独把某个方法切回MySQL。Service DS(dm) public class DmReportServiceImpl implements DmReportService { Override public ListDeviceStatDTO loadStats() { // 走达梦 } DS(master) Override public ListDeviceStatDTO loadStatsFromMysql() { // 方法级别覆盖切回MySQL } }这个特性在实际业务里很有用。比如一个类的大部分查询都在达梦但个别兼容查询需要从MySQL的缓存表里取直接方法级DS(master)覆盖即可不需要拆类。3.3 动态数据源切换原理几句话讲清楚刚开始用DS时可能会觉得它像魔法加个注解数据源就切过去了。其实底层原理并不复杂。Spring提供过一个抽象类AbstractRoutingDataSource它本身不实际管理连接而是通过determineCurrentLookupKey()方法从某个上下文里取出一个key再根据key查找真正对应的DataSource。dynamic-datasource就是在这个基础上做的增强它内部维护了一个Map里面装着你配置的所有数据源key就是master、dm这些名字。每次要获取数据库连接时会先从ThreadLocal里读当前线程应该用的数据源key然后路由到对应的DataSource去拿连接。而DS注解只是一个切入点标记。dynamic-datasource启动时会注册一个AOP切面拦截所有标注了DS的方法在方法执行前把注解上的key值push到ThreadLocal方法执行完毕后从ThreadLocal里poll掉。这样整个数据源选择过程就变成了AOP放key、路由数据源取key、真实数据源拿连接三步。理解了原理后面排查切换不生效、事务下切换失效这类问题就有方向了。所有多数据源的坑几乎都集中在“key有没有被正确放到ThreadLocal”和“连接是否已经被事务提前绑定”这两个点上。4. 实际操作与踩坑记录4.1 驱动加载问题找不到dm.jdbc.driver.DmDriver配置写完之后第一次启动服务就遇到报错日志里清清楚楚写着找不到驱动类dm.jdbc.driver.DmDriver。排查下来原因有两个。一个是我一开始把驱动jar包放在服务器某个目录下但没安装到本地Maven仓库打包时根本不会打进去。这个问题通过前面说的mvn install:install-file解决了。另一个原因是达梦驱动的包名比较隐蔽。很多国产数据库的JDBC驱动类名跟厂商名强相关比如com.kingbase8.Driver这种。达梦就不一样类名是dm.jdbc.driver.DmDriver如果不小心写成了com.dameng.jdbc.Driver启动照样报错。这个最好在写完配置后顺手确认一下jar包里的实际类名jar tf DmJdbcDriver18.jar | grep Driver.class看到dm/jdbc/driver/DmDriver.class就说明配置写对了。这个命令建议所有接达梦的人先跑一遍比看文档快得多。4.2 SQL方言差异的坑分页、主键、时间函数配置好了、驱动也加载了真正开始调SQL的时候才是大头。MySQL和达梦虽然都支持SQL标准但方言差异能把人磨崩溃。最典型的是分页。MySQL里习惯LIMIT 10, 20这种写法在达梦8的MySQL兼容模式下也能跑通但是一旦SQL里用了比较复杂的子查询、关联查询达梦对LIMIT语法还是会有自己的解析约束不是百分百兼容。更稳妥的分页方案是借助MyBatis-Plus自带的分页插件它最终生成的分页SQL会跟随当前数据源的方言走。也就是说切换到达梦后分页插件会自动生成达梦能接受的分页语句不需要你手写。主键自增也是一样。MySQL建表时用AUTO_INCREMENT达梦里则是IDENTITY自增列两者在表结构和插入时的主键生成策略上都有区别。如果你用了MyBatis-Plus实体上一般通过TableId(type IdType.AUTO)控制但底层数据库的主键生成方式必须跟建表语句匹配SQL里不要混写。时间函数同样容易踩。MySQL里常用NOW()、DATE_FORMAT()达梦虽然是SYSDATE这一套但兼容模式下对NOW()也能处理一些。真正麻烦的是业务SQL里那种复杂日期计算比如按周统计、按季度分组两边函数的参数差异会直接导致查询报错或结果不对。我的应对策略是把公共SQL统一维护成“两边都能跑”的写法数据特征差异大的地方单独为达梦编写一份XML。比如某条报表聚合SQL在MySQL上跑得好好的到了达梦上就是某段函数不兼容那就直接在对应的Mapper XML里用${dbType}之类的代码判断或者准备两个语句实现DB层面实在统一不了的在代码层做兼容不要硬抠SQL的通用性。4.3 事务边界导致切换失效这是多数据源最经典的坑如果你在一个类的方法上同时标了Transactional和DS(dm)大概率不会按你想象的方式工作。问题出在事务管理器上。Spring开启事务时会提前从数据源里获取一个连接并且把这个连接绑定到当前线程。当你在同一个方法里继续调用其他Mapper想切到另一个数据源时数据源key虽然切换了但事务管理器手里的连接还是原来那一个。这就像你在餐厅已经坐定了桌位服务员开始上菜了这时候你想换到隔壁桌子人家只会告诉你菜已经按现桌上了。举个例子下面这段代码就是典型的反面教材Transactional public void doCrossDbTask() { userMapper.updateMysqlUser(); // 主库、事务连接已绑定 DS(dm) deviceStatMapper.insertDmRecord(); // 你以为切到达梦实际连接还是主库的 }正确的做法是缩小事务边界。跨数据库的操作本质上已经属于分布式事务范畴在没有分布式事务方案的前提下最稳妥的就是让每个库各自独立提交。把会切库的调用拆到不同的Service方法里每个方法各自管理自己的事务或者干脆去掉长事务public void doCrossDbTask() { userService.updateMysqlUser(); // 自己的事务主库提交 dmReportService.insertDmRecord(); // 自己的事务达梦提交 }写代码的时候要想清楚多数据源切换本身只是查询路由它解决不了跨库的原子性问题。如果需要强一致得引入Seata这类分布式事务框架但那是另一个大工程了。以我这个报表项目的场景业务基本都是读多写少而且两个库的数据允许异步最终一致所以不需要分布式事务拆分事务就是最优解。4.4 打包与部署的坑服务在本地跑通之后下一步就是打包部署。这一关我差点翻车。由于达梦驱动是手动安装到本地Maven仓库的如果你们的构建环境用的是独立打包机或CI流水线它本地仓库里根本没有这个jar打包时就会提示依赖找不到。我当时的解决方案有两个第一把jar文件放到项目里的lib目录通过system scope引入dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.2.192/version scopesystem/scope systemPath${project.basedir}/lib/DmJdbcDriver18.jar/systemPath /dependency这种方式简单直接但打包时要注意Spring Boot的重打包含system scope依赖如果打不出来还得在spring-boot-maven-plugin里配置includeSystemScope参数。第二在CI流程里先执行一次mvn install:install-file再把依赖的scope改回compile比较干净但多一步安装命令。另外Nacos配置文件修改之后不要忘了发布和重启服务。若依微服务里所有配置都来自Nacos你在本地改Nacos的yaml后配置中心会推给客户端但数据源这类初始化比较重的配置保险起见重启一下服务否则可能命中的还是旧连接池配置。4.5 怎么确认切换真的生效多数据源配置好之后如何证明它真的在切换这个环节不要靠猜有明确的验证手段。先把动态数据源的切换日志打开在Nacos配置里加一行logging: level: com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder: debug这样在调用带DS注解的方法时日志里会打出切换数据源的key记录。比如我调用listDeviceStats()接口后日志里会明确出现类似“动态数据源-切换到dm”的信息。光看日志还不够更硬核的验证是直接翻Druid监控页面。若依微服务内置了Druid监控登录监控页后在数据源列表里能看到多个数据源标签分别展示各自当前的活跃连接数、空闲连接数、执行SQL数。分别调主库接口和达梦库接口然后去监控页对比SQL执行次数哪个数字在涨就说明连接真的落在了哪个库上。我当时的调试方法很简单在一个Controller里写两个测试接口一个返回MySQL里的用户名列表一个返回达梦库里的设备统计列表。两个接口串行调用看返回结果、看日志、看Druid监控三个维度一起确认没有问题再往上封装业务。5. 常见问题速查与建议5.1 常见问题速查表把这一轮改造里遇到的、以及周边同事踩过的问题整理成下表方便遇到类似情况时快速对照排查。问题现象可能原因解决办法启动报ClassNotFoundException: dm.jdbc.driver.DmDriver驱动未安装到本地仓库或类名写错执行mvn install:install-file确认实际驱动类名配置了DS但查询仍走主库切面没扫描到方法或方法被事务提前绑定连接检查DS标注位置拆分事务边界达梦上SQL执行报语法错误分页、日期函数等MySQL方言写法在DM不兼容用分页插件独立编写DM的Mapper XML找不到数据源key的异常注解value写错或strict模式下key不存在核对yaml里的key名称生产环境建议stricttrue打包后运行时报驱动缺失system scope依赖未打入Spring Boot Fat JAR配置includeSystemScope或先install到本地仓库同一个Service内切换失效DS标注在接口而不是实现类被代理拦截不到把DS标注在实现类方法上5.2 写代码时的几个建议经过这一轮实际改造总结几条接地气的代码建议。第一DS注解优先写在Service实现类的方法上不要写在接口方法上。Spring AOP默认基于动态代理接口上的注解在部分场景下会被代理机制忽略导致注解不生效。方法级别的注解优先级又高于类级别这个规则用熟了一个类里混用多数据源也很从容。第二数据源的key最好用常量统一管理别在代码里到处写dm、master这种裸字符串。一旦key改名全靠全局搜索会改到崩溃。定义一个DataSourceKey常量类或枚举哪怕多写几行代码维护起来舒服很多。第三数据源切换要在Service层做不要下放到Mapper层。Mapper层面写DS虽然也行但事务边界会变得很难控制而且Mapper的复用性会受约束。Service层是业务逻辑的入口在这里切换数据源的语义更清晰。第四strict参数生产环境一定要开true。开发阶段用false是可以理解的毕竟配置不全或者key写错时还能兜底走主库。但生产环境兜底到主库是个定时炸弹写错的key不会报错只会把达梦的查询默默落到MySQL上一旦SQL执行结果不同数据对不上排查成本极高。第五连达梦库的连接池参数一开始不要给得太激进。很多国产数据库在并发压力测试下的表现跟MySQL还是有差异的先把min-idle和max-active调保守运行一段时间观察Druid监控里的活跃连接和等待数再逐步放大。这比一上来就给大池子稳健得多。6. 一些值得留住的个人体会这次改造做完我心里最大的感触是多数据源本身其实不难它只是一个路由问题。真正消耗时间的全在业务边界和SQL方言的兼容上。dynamic-datasource把数据源切换这件技术层面的细节封装得足够好了但“哪些方法该走MySQL、哪些方法该走达梦、跨库操作要不要事务”这些问题没有任何组件能替你回答。另外国产数据库的成熟度确实在肉眼可见地提升达梦对MySQL语法的兼容已经做得很不错了。但做项目不能把“兼容”当成“完全支持”接国产库之前最好先拿核心业务SQL在目标库上做一轮方言适配测试别等上线了再去排查语法报错那个阶段改SQL的成本会成倍放大。如果你们也正在若依微服务里做类似的多数据源改造我的建议是先把数据源边界画清楚再动手写代码。哪张表归MySQL、哪张表归达梦哪些接口是跨库聚合的这些理清之后配置上用一个starter、几个注解就能解决。项目上要是碰到更好的解法欢迎交流。