资讯动态

若依微服务整合MySQL与达梦DM双数据源:基于@DS注解的动态切换实践

发布时间:2026/9/30 3:05:20 来源:尧图企业网站定制
1. 需求背景与多数据源方案选型1.1 这个需求到底是怎么来的说实话最开始接到这个需求时我第一反应是“不就是加个数据源嘛”。但真正了解业务后才发现这事得小心。项目用的是若依微服务版 RuoYi-Cloud。核心库是 MySQL里面有用户、角色、菜单、部门这些标准表而新的电子档案模块按客户要求必须存储在达梦 DM 里档案表还要和系统表做关联查询。也就是说在一个微服务里既要读 MySQL 的 sys_user又要读 DM 里的 archive_info。这种需求在国产化项目里很常见。存量业务继续跑 MySQL新业务模块落在达梦 DM 或者其它国产数据库上或者反过来总部用达梦分公司还在 MySQL。那开发人员就不得不面对一个技术问题怎么在同一个 Spring Boot 服务里配置两个数据源并且按业务场景动态切换。1.2 多数据源方案对比与选型做多数据源常用的路子大概有下面几种。第一种手动配置两个 DataSource。在配置类里分别建 MySQLDataSource 和 DmDataSource然后给两个 SqlSessionFactory 或两个 JdbcTemplate。这种做法的好处是结构清晰不引入额外依赖坏处是代码特别繁琐每个 Mapper 都要指定 SqlSessionTemplate每个 Service 要注入对应的 Dao后续如果再加数据源改动量成倍增加。适合数据源固定、业务简单的老项目不适合需要快速迭代的若依微服务。第二种用 Spring 的 Primary Qualifier。这种方法本质上还是手动配置只是把数据源交给 Spring 管理再靠限定符区分。但实际操作起来稍不留神就会注入错 Bean尤其在多个 DataSource 同时存在的情况下MyBatis-Plus 自动配置极容易抢到错误的 DataSource。第三种用动态数据源组件。目前社区里比较成熟的是 baomidou 的 dynamic-datasource-spring-boot-starter也就是 MyBatis-Plus 团队出的。它支持通过 DS 注解在方法或 Mapper 级别动态切换数据源底层实现了一个 DataSource 路由对业务代码侵入很小。我最后选的就是这个方案。原因很简单若依微服务的 ORM 体系本身就以 MyBatis-Plus 为主用同一个生态的轮子整合成本最低踩过的坑也相对少。这里还要说一下不是 ShardingSphere 不好如果项目本来就要做分库分表、读写分离那 ShardingSphere 是正确的选择但仅仅是“一个服务连两个库、偶尔切一下”引入重量级中间件会让排查问题更难。最终技术栈定为RuoYi-CloudSpring Boot 2.7 Spring Cloud Alibaba、MyBatis-Plus、Druid 连接池、达梦 DM8。2. 环境准备与前置条件2.1 若依微服务端的改造范围RuoYi-Cloud 和 RuoYi-Vue 的单体版不一样它本身分了 gateway、auth、system、file 等模块配置统一放在 Nacos 里。所以改多数据源时一定要想清楚改哪个服务。我的实际场景是电子档案管理所以只改ruoyi-modules/ruoyi-system这个服务即可其他服务不需要动。如果你准备让新的独立业务跑 DM更推荐直接新建一个业务微服务模块这样数据源边界清晰压力也只影响独立服务。这里提个建议不要把多数据源配置直接复制到每个服务里。虽然复制最快但后面维护 Nacos 配置会很痛苦。把公共连接信息抽到 Nacos 的共享配置里服务只保留数据源名字和业务相关参数这样团队协作时能少很多麻烦。不过这个属于团队规范不是必须。2.2 达梦 DM 端的数据库准备达梦 DM8 安装完成后默认端口 5236默认账号 SYSDBA。第一步先建立业务用户和模式。DM 里的“用户”和“模式”概念可以简单理解成创建用户的同时会创建一个同名模式表放在模式下面。实际连接时用的用户名和 schema 通常是同一个名字。我习惯用 SQL 来建用户和授权避免图形工具容易忽略大小写问题CREATE USER DM_ARCHIVE IDENTIFIED BY Passw0rd; GRANT DBA TO DM_ARCHIVE;提示达梦默认是大小写敏感的。如果 SQL 里不加双引号标识符会被自动转成大写。所以上面我直接写了DM_ARCHIVE大写这样后续表名、字段名也尽量统一用大写能省掉很多啼笑皆非的错误。不要问我为什么强调这个后面踩坑章节会细说。然后在 DM_ARCHIVE 模式下建表。举个例子CREATE TABLE DM_ARCHIVE.ARCHIVE_INFO ( ID BIGINT PRIMARY KEY, CREATE_TIME DATETIME, SUMMARY VARCHAR(500) );顺便说一句如果你以前用的是 Oracle 方言DM8 支持兼容 Oracle 模式如果团队更习惯 MySQL也可以在初始化实例时选择 MySQL 兼容模式。但我们项目里两个库同时存在SQL 风格不可能完全统一我在建表时尽量用标准的、两边都认的写法能避免很多运行时错误。2.3 依赖与驱动引入达梦的 JDBC 驱动不会出现在 Maven 中央仓库里这是很多人最开始卡住的地方。DM8 安装目录下一般会有drivers/jdbc/DmJdbcDriver18.jar对应 JDK8 及以上环境。你需要把它手动安装到本地仓库或者放到企业私服。Install 到本地仓库的命令很简单mvn install:install-file -DfileDmJdbcDriver18.jar -DgroupIdcom.dm -DartifactIdDmJdbcDriver18 -Dversion1.8 -Dpackagingjar然后在模块的 pom.xml 里追加依赖dependency groupIdcom.dm/groupId artifactIdDmJdbcDriver18/artifactId version1.8/version /dependency dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version3.5.2/version /dependency驱动类名是dm.jdbc.driver.DmDriver连接串格式是jdbc:dm://ip:5236。如果你在配置时看到“Cannot load driver class”十有八九是 jar 没打进去。3. 核心配置双数据源接入步骤3.1 修改 Nacos 中的配置文件RuoYi-Cloud 中 system 服务的配置在 Nacos 里名字一般是ruoyi-system-dev.yml。我改动的核心就是spring.datasource这一段。原本若依的配置类似这样spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: filters: stat initial-size: 5 max-active: 20 min-idle: 5引入 dynamic-datasource 后把它替换成如下结构spring: datasource: dynamic: primary: master strict: false datasource: master: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry-cloud?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 dm: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://localhost:5236 username: DM_ARCHIVE password: Passw0rd这段配置里的primary: master表示默认走 MySQLstrict: false表示如果切换到不认识的数据源时回退到主数据源而不是直接报错。在开发阶段我会把strict设成true这样能快速发现拼写错误上线前再改回false防止某个数据源名称不一致导致整个接口挂掉。如果每个数据源想单独控制连接池大小可以在对应数据源下面增加连接池参数。比如 dm 数据源给个更小的上限dm: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://localhost:5236 username: DM_ARCHIVE password: Passw0rd initial-size: 3 max-active: 15 min-idle: 3注意如果你的项目里使用 HikariCP 而不是 Druid那就不需要type这行。若依默认集成了 Druid想继续在连接池层面通用最好保持type和数据源配置一致。3.2 通过 DS 注解实现数据源切换配置完成后切换的写法很简单。在 Service 类或方法上打DS(dm)这个类里的 Mapper 操作就会自动走到达梦库。DS(dm) Service public class ArchiveServiceImpl implements ArchiveService { Autowired private ArchiveInfoMapper archiveInfoMapper; Override public ListArchiveInfo listArchive(ArchiveQuery query) { return archiveInfoMapper.selectList( Wrappers.ArchiveInfolambdaQuery() .like(ArchiveInfo::getSummary, query.getKeyword())); } }有时候同一 Service 里要同时访问两个库最好不要在类级别打DS而是在方法上分开标注Service public class ArchiveServiceImpl implements ArchiveService { Autowired private ArchiveInfoMapper archiveInfoMapper; Autowired private SysUserMapper sysUserMapper; DS(dm) public ListArchiveInfo listArchive() { return archiveInfoMapper.selectList(null); } DS(master) public SysUser getUserById(Long userId) { return sysUserMapper.selectById(userId); } }在实际项目里我更推荐在 Mapper 接口上标注数据源而不是在 Service 方法上。因为 Mapper 是数据访问的边界一眼就能看出这个 Dao 对应哪个库。比如DS(dm) Mapper public interface ArchiveInfoMapper extends BaseMapperArchiveInfo { }这样做以后即使是别的 Service 偶尔用到 ArchiveInfoMapper也会自动走 DM不太容易出现“Service 忘了加注解导致数据源切错”的低级事故。3.3 事务边界不是在方法上写个注解就完事多数据源下最坑的其实是事务。如果你在一个方法上同时加Transactional和DS(dm)大多数情况下看起来没什么问题但只要后面有人在同一个事务方法里调用了另一个数据源的操作连接就会乱掉。我遇过一次特别典型的Service 方法声明走 DM里面先存了 DM 的表然后不小心调了一个走 MySQL 的 Mapper 查询结果 Spring 事务已经绑定了 DM 连接MySQL 那步连接拿不到直接报“Cannot switch data source in transaction”。要规避这个问题靠的是代码设计层面把事务边界切干净。我的做法是单个数据源内的写操作可以使用Transactional跨数据源的操作绝对不用一个事务包起来拆成两个 Service 方法各自管各自的事务如果实在需要强一致那就引入分布式事务组件比如 Seata但成本很高能不用就不用。如果只是查询不在一个事务里DS切换基本不会出问题。3.4 写一个临时接口验证数据源是否生效配置这一步最怕“看起来没问题实际走错库”。我会在联调前先写一个临时接口验证两个数据源都能连通。DynamicRoutingDataSource 可以拿到当前所有数据源直接注入它RestController RequestMapping(/demo/ds) public class DataSourceCheckController { Autowired private DynamicRoutingDataSource dataSource; GetMapping(/check/{name}) public String check(PathVariable String name) { if (dataSource.getDataSource(name) ! null) { return 数据源存在: name; } return 数据源不存在: name; } }再配合一个简单的 Mapper 查询分别从 DM 和 MySQL 各查一条数据看返回结果是否来自预期库。这个小接口上线前记得删掉否则外部探测可能会暴露数据源信息。4. 实操过程与踩坑记录4.1 驱动安装DM 的 jar 包不在中央仓库前面提到了驱动安装但实际操作时还有个容易忽略的点Maven 打包时手动安装到本地仓库的 jar 只在本地有效。如果你们是多人团队用 Jenkins 打包构建机器上如果没有这个 jar就会报依赖解析失败。所以最好把 DM 驱动上传到私有 Nexus 仓库或者用 Maven 的systemPath方式引入本地 lib 目录具体看团队规范。我这次是直接把 jar 放在项目lib目录利用 pom 里的systemScope引入的虽然不算优雅但适合快速交付。dependency groupIdcom.dm/groupId artifactIdDmJdbcDriver18/artifactId version1.8/version scopesystem/scope systemPath${project.basedir}/lib/DmJdbcDriver18.jar/systemPath /dependency这种做法有个副作用如果做成可执行 jar 部署打包时容易丢掉lib下的 jar所以最好在spring-boot-maven-plugin里配置includeSystemScope参数否则部署时会出现驱动类找不到。这也是我在测试环境被反复折腾后才发现的问题。4.2 表名大小写与 SQL 方言兼容达梦 DM 默认对大小写极其敏感。第一次做联调时我建表用的表名是archive_info但在 DM 的图形工具里看却变成了ARCHIVE_INFO。程序里查询archive_info直接报“表或视图不存在”。解决方案有两种。第一种所有标识符统一用大写SQL 里也写大写第二种建表/建用户时使用双引号包住小写标识符例如CREATE TABLE archive_info这样表名就能保持小写程序里也用双引号或保持同样的小写写法。我选择了第一种因为双引号在原生 SQL 里很容易被忽略团队协作时容易产生混乱。除了大小写SQL 语法也是个麻烦点。MySQL 的LIMIT ?在达梦的 Oracle 兼容模式下是不支持的得用ROWNUM反过来达梦的VARCHAR2在 MySQL 里需要对应到VARCHAR。如果项目里用了 MyBatis-Plus 的QueryWrapper这些差异基本会被框架屏蔽掉但一旦涉及到自定义 SQL就要特别小心。我定了一条规矩凡是双库都要执行的 Mapper 方法统一使用 MyBatis-Plus 提供的条件构造器确实需要写原生 SQL 的必须写成兼容两边方言的写法或者放到各自的 Mapper 里分开管理。4.3 MyBatis-Plus 分页与 DM 方言分页插件绝对是我这次踩得最久的坑。若依微服务里原本就配置了 MyBatis-Plus 的分页拦截器默认情况下它可以自动识别连接类型。但加了达梦数据源后jdbc:dm://连接在部分旧版本 MyBatis-Plus 中识别不到导致分页 SQL 被生成成 MySQL 方言执行时直接报错。正确做法是别把数据库类型写死让拦截器在运行时根据连接自动判断。如果用的是 MyBatis-Plus 3.5.xBean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor(); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; }不需要指定DbType.DM。如果自动识别确实有问题的版本可以考虑在启动时把 DM 的 JdbcUrl 注册到JdbcUtils的映射里或者在PaginationInnerInterceptor的setDbType方法里写动态判断逻辑但这些都是补丁式做法最好优先升级版本。提示网上很多教程写new PaginationInnerInterceptor(DbType.DM)那是针对单一数据源的。如果你一边连 MySQL 一边连 DM这样写死会让 MySQL 的分页也走达梦方言翻车概率极大。我一开始就是被这种教程带偏了折腾了大半天。4.4 和 Druid 监控并存时的配置细节若依自带的 DruidConfig 里通常会定义一个Primary数据源用来支持监控页面。把 dynamic-datasource 引进来后两者会发生冲突轻则启动时提示 Bean 重复重则监控页面不显示达梦连接池。我最后选择的方式是把若依原有的 DruidConfig 直接去掉数据源统一交给 dynamic-datasource 管理每个数据源单独指定 type 为DruidDataSource。这样 Druid 连接池的核心能力还在只是不再单独暴露一个Primary的 DataSource。如果你是刚上手不想动原来的 DruidConfig也可以继续保留但要确保动态数据源配置里的数据源不会和原来的 DataSource Bean 互相竞争。实际操作中保留两份“数据中心”很容易导致连接管理错乱不推荐。4.5 联调实测记录从报错到跑通这里放一段真实的排错时间线给大家一个直观感受。第一次只改了配置启动时直接报“Cannot load driver class: dm.jdbc.driver.DmDriver”原因是驱动 jar 没进入 classpath。把 jar 放进 lib 并重新打包后服务起来了但查询 DM 表报“表或视图不存在”一查发现建表时用了小写DM 自动转成了大写。把表名全部改成大写后普通查询通过了。紧接着分页查询又出错日志里出现LIMIT关键字我就知道是分页方言不对。排查到 MyBatis-Plus 拦截器配置把写死的DbType.DM去掉后两个库的分页都正常了。整个过程看起来就三个报错但前前后后花了快一天核心问题都是配置细节。5. 常见问题排查与性能建议5.1 排查思路从环境到代码逐层确认多数据源的问题表面症状可能一样但源头千差万别。我总结了一套排查顺序基本能覆盖八成情况先确认驱动包是否真的进了最终运行的 classpath。如果是 IDE 里跑看依赖树如果是部署环境用jar tf检查 BOOT-INF/lib 下有没有DmJdbcDriver18.jar。再确认数据源配置是否加载。可以在 DM 数据源对应的 Mapper 上加一个临时接口返回DynamicRoutingDataSource里的数据源信息或者直接看启动日志里有没有“dynamic-datasource”的初始化记录。然后看 SQL 兼容性。把 MyBatis 日志级别调到 DEBUG抓出实际执行的 SQL复制到 DM 客户端里手动跑一遍很多问题一眼就能看出来。最后查切换是否生效。如果发现查询没有走 DM优先检查DS是不是加错了位置以及是不是在同一个类内部调用了带注解的方法。Spring AOP 不会拦截同类内部调用所以this.listArchive()这样的写法是无效的必须注入代理对象或者把方法拆到不同的类。5.2 连接池参数与性能实测多数据源意味着多套连接池参数不能照抄。以我这个项目为例MySQL 是核心业务库压力大所以给了 20 个最大连接达梦库只是档案查询读多写少最大连接控制在 15 以内初始化 3 个就够了。连接池空闲检测、测试语句这些参数也最好单独调整。动态数据源对每个数据源是独立的连接池千万不要把连接池参数写到全局公共配置里否则一个库的参数无法适应另一个库的场景。达梦的连接数不像 MySQL 那么多如果应用端连接池上限设置太大数据库端的 license 或最大会话数可能会成为瓶颈。我建议先和 DBA 确认最大会话数再反推应用连接池上限。虽然这个项目并发量不大但这个习惯值得保留。5.3 数据库密码加密与安全建议若依框架本身有基于 Druid 的密码加密工具但如果使用了 dynamic-datasource很多自加密的拦截器可能不会自动覆盖到每个数据源。我的做法是在配置层使用外部化配置把数据库密码放到 Nacos 的加密配置项里应用启动时解码。或者在 Spring 配置类里对DruidDataSource做一层解密处理保证 DataSource 初始化前密码已经是明文而配置中心存的是密文。总的来说多数据源环境下的安全相对于单数据源更复杂因为每个数据源的密码、账号、最小权限都要单独梳理。达梦那边我给的是业务账号DM_ARCHIVE而不是 SYSDBA权限也尽量按表授权不轻易给 DBA。这个习惯能避免很多数据安全事故。5.4 问题速查表拿来即用现象可能原因解决办法启动报 Cannot load driver classDM 驱动 jar 未进入 classpath检查 pom 依赖或 lib 目录驱动类为 dm.jdbc.driver.DmDriver查询报表或视图不存在表名大小写不一致统一用大写或创建对象时用双引号保留小写分页 SQL 出现 LIMIT 但达梦不支持分页方言被写死为 MySQL不传 DbType让拦截器自动识别或升级 MyBatis-PlusDS 切换没生效同类内部方法调用把方法拆到不同类或注入自身代理对象事务内无法切换数据源Spring 事务已绑定连接跨数据源事务拆分避免一个事务里切换连接池初始化失败数据源连接参数不对分别核对 jdbc url、账号、密码、端口 5236Druid 监控页面无达梦连接池原有 DruidConfig 与新数据源冲突统一交给 dynamic-datasource 管理6. 给后来者的实在建议6.1 优先想清楚数据源边界别为了省事把所有数据源塞到一个服务里。如果业务模块边界清晰新建一个微服务专门对接达梦比在 System 服务里折腾多数据源要舒服得多。多数据源真正适合的场景是“存量加增量”存量 MySQL 不能动新增表又必须入 DM。我在这次项目里后续又上了几个新模块都开始用独立服务的方式接入维护成本明显下降。6.2 把表结构和 SQL 规范定在项目初期最后想强调的还是规范。多数据源并存的工程最怕“这边 MySQL 下划线那边 DM 大写SQL 风格乱七八糟”。我吃过亏后直接在团队文档里写了两条铁律一是所有跨数据源使用的表名、字段名统一用大写二是所有公共查询优先用 MyBatis-Plus 的条件构造器避免手写方言不兼容的 SQL。回到开头那个问题若依微服务环境下配置 MySQL 达梦 DM 多数据源确实不难难的是细节。驱动包的来源、大小写规则、事务边界、分页方言这些点只要有一个没考虑到就会在联调阶段冒出来打你一巴掌。希望这篇记录能帮你少走一些弯路尤其是“不要写死分页方言”和“不要在事务里切数据源”这两个坑我印象太深了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑