项目标题里同时出现“若依微服务”和“MySQL + DM”,说明你大概率正在做一件很常见的事:把一个原本只连 MySQL 的若依微服务项目,改造成能同时访问 MySQL 和达梦数据库(DM)的双数据源工程。而且这个“多数据源”不是DEMO里那种糊弄人的玩具配置,是真要落到业务上、能稳定切换查询的那种。
这段时间我刚好把一个若依微服务模块从“单 MySQL”改造成“MySQL 主库 + DM 从库”,中间翻过不少资料,也踩了几个不太容易发现的坑。更难受的是,若依本身版本多、模块杂,网上文章东一句西一句,很难一口气照着做成功。所以我把这次完整的过程沉淀成一篇能直接照着操作的实操笔记,从架构思路、依赖改造、Nacos 配置,到业务代码里的@DS注解切换、事务边界、分页方言,以及最后的高频排错,全部串起来讲清楚。
如果你正卡在“驱动装不上”“切换不生效”“分页 SQL 在 DM 上报错”这几个典型环节,这篇文章应该能直接给你答案。
1. 项目背景与多数据源需求拆解
1.1 为什么会在若依微服务项目中遇到多数据源
很多团队最初选若依,就是看中它开箱即用:Nacos 做注册配置中心,各微服务按业务边界拆分,system管权限、job管定时任务、auth管认证,数据层统一走 MySQL。这套架构在纯新建系统里很舒服,但一旦涉及数据迁移、系统对接、历史系统改造,问题就来了。
我这次遇到的实际场景是:新平台用若依微服务跑,主业务数据在 MySQL 里,但一个老项目的数据仍然留在达梦数据库 DM8 中。而且老项目不会很快下线,新平台实时业务需要频繁读取 DM 里的存量数据。最直接的想法就是把老库直接迁到 MySQL,但业务方不同意,因为老系统还在持续写入,双写方案又太复杂。于是退而求其次,让若依的某个微服务同时持有两个数据源:默认走 MySQL,需要查老数据时切到 DM。
这种情况在政务、军工、金融类的国产化替代项目里特别典型。达梦数据库本身兼容 Oracle 模式,很多老系统从 Oracle 迁到 DM 后仍然跑得好好的;而新开发的业务系统往往选 MySQL 生态,两边共存是常态。所以,与其说是“一个服务连两个库”,不如说是在一套微服务框架里维护两套不同数据库生态的访问能力。
1.2 两条主流实现路线:多套会话工厂与动态数据源
第一次接触多数据源的人,最容易在网上搜到两类方案,这里先说清楚它们的本质区别,避免走弯路。
第一类:给每个数据源单独创建一套SqlSessionFactory和TransactionManager。也就是说,一个服务里同时存在两套 MyBatis 配置,每套对应自己的DataSource,业务代码里必须明确指定用哪一套。好处是很“正统”,坏处是配置量巨大,每个 Mapper 都要确认绑定到哪个工厂,事务边界也容易乱。对于若依这种模块化很深的框架,这种方案侵入性太强,改起来烦得很。
第二类:用动态数据源框架,比如dynamic-datasource-spring-boot-starter,把所有数据源交给一个路由管理器统一持有。运行时通过 AOP 拦截带有@DS("xxx")的方法,动态把当前线程绑定的DataSource切到指定数据源,方法执行完再自动切回来。这个思路本质上是把“多数据源”抽象成一个“数据源路由表”。
我最后选的就是第二类。原因是若依多个分支版本里其实已经内置了 dynamic-datasource 的依赖,或者至少预留了类似的结构,改动最小、最容易落地。而且它的@DS注解是方法级的,在 Service 层一切换就能隔离,不需要动 Mapper XML。
1.3 为什么最终选择动态数据源方案
除了侵入性小,还有一个很现实的原因:统一连接池管理。若依默认配 Druid,dynamic-datasource 对它支持得比较好,可以给每个数据源单独配置连接池参数。比如 MySQL 主库并发高,连接数给大点;DM 老库只是偶尔查报表,连接数给小点,互不影响。这在多数据源场景下非常重要,因为两个库的连接池如果完全搅在一起,一个库出问题会把另一个也拖垮。
另外,dynamic-datasource 支持primary默认数据源设置。也就是说,就算某个业务方法忘了加@DS,它会老老实实走主数据源,不会因为没注明路由就报错。这个特性对老模块迁移特别友好,可以先不动的代码,新写的功能再逐步切其他数据源。
2. 环境准备与依赖改造
2.1 版本组合建议
多数据源成功的关键,首先在版本匹配。我这里强烈建议先确认你的若依微服务版本再动手,不同版本的若依封装的依赖差别很大,网上配置抄错一点就是一堆诡异报错。
如果你用的是当前主流的若依微服务版本(大致对应 Spring Cloud Alibaba 体系),常见的组合是:
| 组件 | 推荐版本 |
|---|---|
| Spring Boot | 2.5.x 或 2.6.x |
| Spring Cloud | 2020.0.x 或 2021.0.x |
| Nacos | 2.x |
| MyBatis | 3.5.x 以上(若依自带) |
| MySQL 驱动 | mysql-connector-java 8.0.x |
| 达梦 JDBC 驱动 | DmJdbcDriver18 或 DM8 自带版本,需根据数据库小版本确认 |
| dynamic-datasource | 3.5.x 及以上 |
有两点要特别提醒:第一,达梦 JDBC 驱动的版本不是越新越好,要和 DM8 服务器的版本对齐,否则连接时可能出现协议不兼容;第二,dynamic-datasource 如果你是自己引入的,注意和 Spring Boot 版本兼容,3.5.x 对 Spring Boot 2.x 支持得比较稳。
2.2 达梦 JDBC 驱动的获取与安装
达梦数据库的 JDBC 驱动不像 MySQL 那样随便从 Maven 中央仓库拉。最常见的两种拿法:
一种是从达梦数据库安装目录里找,路径一般是%DM_HOME%/drivers/jdbc/,里面会有DmJdbcDriver18.jar或者DmJdbcDriver8.jar。根据服务端版本选合适的,拷到本地。
另一种是通过 Maven 安装到本地仓库。如果你公司内部有私有 Maven 仓库,还可以把驱动上传上去,方便多个模块引用。命令大致是:
mvn install:install-file -Dfile=DmJdbcDriver18.jar -DgroupId=com.dameng -DartifactId=DmJdbcDriver18 -Dversion=18.x -Dpackaging=jar安装完成之后,在pom.xml里声明依赖:
<dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>18.x</version> </dependency>如果你用私有仓库,安装命令换成mvn deploy:deploy-file上传到远程仓库即可。驱动类的名称要记好,后面配置里要用:
dm.jdbc.driver.DmDriver这也是网上很多文章容易写错的点,有人写dm.jdbc.driver.DmDriver,有人写dm.jdbc.driver.DmDriver大小写不一致,实际上这串类名是严格区分大小写的。
2.3 重要模块的 Maven 依赖调整
若依微服务是多模块结构,依赖不能乱加。你要先明确:到底哪个服务需要连 DM?多数据源配置并不是所有服务都能无脑套的。
一般业务系统会把数据访问集中在ruoyi-system、ruoyi-job、或自己扩展的业务服务模块。你需要给这些模块的pom.xml增加两样东西:一是达梦 JDBC 驱动依赖,二是确认 dynamic-datasource 相关依赖已经在ruoyi-common-datasource里存在。
我这次做法的参考步骤如下:
- 打开目标服务的
pom.xml,它通常已经引用了ruoyi-common-datasource或ruoyi-common-mybatis。 - 查看
ruoyi-common-datasource的pom.xml,确认里面是否有dynamic-datasource-spring-boot-starter的依赖。如果没找到,就手动加进去:
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.2</version> </dependency>- 在目标模块中增加达梦 JDBC 驱动:
<dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>18.x</version> </dependency>这里有个非常容易踩的坑:有些若依项目的ruoyi-common-datasource模块默认依赖里,会把旧版 dynamic-datasource 排除掉,换成自定义的数据源路由。遇到这种情况,需要先把自动装配类看清楚,否则你就算在 Nacos 里写满配置,Spring 也没有加载 dynamic-datasource 的自动配置,@DS注解永远不会生效。
建议在改造前做一次确认,直接搜索整个 common 模块下是否包含DynamicDataSourceAutoConfiguration或@DS对应的DynamicAopAdvisor。找不到就补依赖,找得到就继续。
3. 多数据源配置实操:从 Nacos 到连接池
3.1 Nacos 中 dynamic 数据源树结构怎么排
若依微服务里,数据库连接配置一般不在本地application.yml,而是统一放在 Nacos 配置中心。你要修改的是对应服务的数据源配置文件,名称通常是ruoyi-system-dev.yaml或类似结构。
先看原本的配置长什么样,一般是:
spring: datasource: druid: stat: true type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8改成 dynamic-datasource 结构后,应该是这样:
spring: datasource: dynamic: primary: master strict: false datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: root dm: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://192.168.10.25:5236?schema=DM_TEST username: SYSDBA password: SYSDBA druid: initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000注意:原本的driver-class-name、url这些字段都要移动到datasource.master下面,不能还留在原来的spring.datasource顶层。否则 Spring 还是会优先按单数据源去初始化,导致你配置的dm根本不会加载。
3.2 主从数据库配置项逐个说明
上面那串配置里,有几个关键概念你必须搞清楚,不然配置写完了也只会照猫画虎。
primary: master的含义是默认数据源。所有没有标注@DS的方法,最终都会路由到master这个数据源上。对于老代码来说,这就是“无感兼容”的保证,不用给历史 Service 全部加注解。
strict: false的含义比较隐蔽。它控制的是“当你指定了一个不存在的数据源时怎么办”。false表示不报错,回落到默认数据源;true表示直接抛异常。我建议在调试阶段先设成true,这样如果你代码里写了@DS("dm")但配置里没配dm数据源,会立刻暴露问题。上线前再根据情况改回false。
driver-class-name和url不用多说。但达梦数据库的 URL 格式有个细节:jdbc:dm://IP:PORT?schema=xxx。如果达梦数据库建的用户默认 schema 不是你要访问的,必须在 URL 里明确指定 schema。我一开始没带 schema 参数,能连接上,但 SQL 执行时总是找不到表,一度以为驱动有问题,最后才发现是 schema 没指定。
还有连接池参数。如果你继续用druid配置块,它会作为默认共享配置套到所有数据源上。但你也可以给单个数据源单独覆盖,比如:
datasource: dm: druid: max-active: 10这样 DM 数据源就单独用 10 个最大连接,不会抢占 MySQL 的连接池资源。
3.3 若依自带的自动装配能省多少事
有一些若依版本已经集成了 dynamic-datasource 的自动配置,甚至你不需要写任何@Configuration类,只需要在配置中心把数据源结构改成 dynamic 的格式即可。
具体省事在哪?因为dynamic-datasource-spring-boot-starter自带DataSourceAutoConfiguration的替换逻辑,它会扫描spring.datasource.dynamic.datasource下的所有键,为每个键创建独立的DruidDataSource,并注册到路由表中。Java 代码里只需要注入DataSource时用@Autowired或@Resource(name = "dataSource")即可,拿到的其实是路由代理。
如果你的若依版本没有启用自动配置,那就需要自己写一个配置类,手动把配置项绑定到DynamicDataSource。但这种情况比较少见,遇到的话优先检查是否漏了启动类扫描范围或者依赖没引全。
3.4 手动切换数据源栈的正确姿势
@DS注解是常用的,但有些场景不适合用注解。比如在一个长任务里,需要先查 MySQL,再查 DM,再回 MySQL,中间还要动态决定是查 A 还是查 B。这时候纯注解很难表达,就需要手动路由。
dynamic-datasource 提供了线程绑定机制:
DynamicDataSourceContextHolder.push("dm"); try { // 此时的 dataSource 自动指向 dm dmMapper.queryList(); } finally { DynamicDataSourceContextHolder.poll(); }push是入栈,poll是出栈。这个栈设计对嵌套调用很友好:A 方法 push 了dm,里面调用 B 方法又 push 了另一个数据源,B 结束之后 poll 回dm,不会串。
我强烈建议在开发规范里定一个死规矩:手动 push 必须和 poll 配对,且 poll 要放在 finally 里。否则高并发下线程池中的线程会被上一次的数据源标记污染。这个问题极度隐蔽,平时单测很难发现,只有高并发压测时才会突然出现“查询结果莫名指向另一个库”的诡异现象。
4. 业务代码改造与典型用法
4.1 用 @DS 注解做最小侵入切换
@DS注解是这整套方案的使用核心,它可以直接加在 Service 实现类的方法上面。比如:
@Service public class DataQueryServiceImpl implements DataQueryService { @Autowired private TestDmMapper testDmMapper; @Autowired private TestMysqlMapper testMysqlMapper; @DS("dm") @Override public List<DemoData> listFromDm() { return testDmMapper.selectList(); } @Override public DemoData getFromMysql(Long id) { return testMysqlMapper.selectById(id); } }这个方法执行时,testDmMapper就会走 DM 数据源,而且整条调用链上这个数据源会被绑定到当前线程。方法返回后,数据源自动释放,不会影响外层调用。
有个常见疑问:@DS能不能加在 Mapper 接口上?表面上看,dynamic-datasource 支持在 Mapper 上标注@DS,也能生效。但我个人不建议在 Mapper 上标注,因为 Mapper 会被多个 Service 复用,一旦某个 Service 在事务里先查了 MySQL 再调这个 Mapper,标注在 Mapper 上的@DS可能会和事务产生冲突。更合理的做法是把它标注在 Service 方法上,把数据源的选择提升到业务服务层,这样语义更清晰。
4.2 事务边界与数据源切换的执行顺序
这是多数据源配置里最容易翻车的环节。
先看一个反面示例:
@Transactional(rollbackFor = Exception.class) @DS("dm") public void saveDmAndMysql() { dmMapper.insert(); mysqlMapper.insert(); // 这里不会自动切回 MySQL }这里有个陷阱:@Transactional和@DS同时存在时,数据源的切换必须先获取连接,而 Spring 的事务管理是在进入方法时就确定了一个数据库连接。如果你的@DS("dm")安在事务方法上,那么这个事务内的所有 SQL 都会走 DM,哪怕你代码里操作 MySQL 的 Mapper,它实际上也是通过事务线程绑定的 DM 连接去执行,结果要么报错,要么找不到表。
正确做法是分两个事务方法,或者使用REQUIRES_NEW传播行为把不同数据源的操作拆到独立事务中:
@Transactional(rollbackFor = Exception.class) public void saveAll() { saveToDm(); saveToMysql(); } @DS("dm") @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void saveToDm() { dmMapper.insert(); } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public void saveToMysql() { mysqlMapper.insert(); }简单说,多数据源场景下,“一个方法干多个库的活”是伪需求;真正的正确做法是按照数据源拆分事务边界,再在编排层组合调用。这个编排层的代码虽然多写几行,却能避免大量连接错乱问题。
4.3 跨库查询与方言适配实操
业务上最常见的需求是:主数据在 MySQL,但某几个属性要从 DM 老系统里补全。这时候不推荐直接跨库 JOIN,因为两个库分属不同数据库产品,即使都支持标准 SQL,也很难保证分页、函数、排序行为完全一致。更稳的方案是分两次查询,在应用内存中做结果合并。
如果两个库的数据量不大,这个方案性能完全能接受。比如查订单列表先走 MySQL,拿到的结果包含userId,然后根据userId批量查 DM 里的用户扩展信息,在 Java 里Map匹配后组装返回。
另外,达梦数据库兼容 Oracle 模式,SQL 语法上有些地方和 MySQL 不同。比如:
- MySQL 用
LIMIT 0, 10,DM 在 Oracle 兼容模式下更推荐ROWNUM或者FETCH FIRST 10 ROWS ONLY。 - MySQL 的
IFNULL,DM 里要写NVL。 - 字符串拼接,MySQL 是
CONCAT(a, b),DM 兼容 Oracle 用的是a || b。 - 日期函数差异明显,
NOW()在 DM 中要考虑SYSDATE或CURRENT_TIMESTAMP。
所以最稳妥的方式是为 DM 单独写 SQL,条件允许的话,Mapper XML 文件单独建一套,避免在通用 mapper 里硬写方言函数。
分页也要单独说。若依用的是 PageHelper,默认方言通常是 MySQL。PageHelper 对达梦的支持要看版本,新版 PageHelper 已经支持 DM 方言,但还是需要显式指定:
pagehelper: helper-dialect: oracle reasonable: true support-methods-arguments: true如果达梦库是 Oracle 兼容模式,把helper-dialect配成oracle通常可行。如果仍出现分页 SQL 无法解析的情况,说明你的分页方式和 PageHelper 自动检测不匹配,就要考虑在 DM 查询里手动分页,或者通过自定义拦截器处理。
4.4 微服务模块之间的数据源职责划分
若依微服务是多个独立服务通过 Nacos 互相调用。多数据源配置的粒度要按服务划分,我建议不要把“一个服务连无数个数据源”当成默认设计。
举个例子,ruoyi-system服务主要负责用户、角色、权限,它一般只需要连 MySQL。ruoyi-job服务如果涉及定时任务调度,也可以连主库。而真正对接 DM 的业务模块,比如你的业务服务business服务,才需要引入 DM 驱动和多数据源配置。其他服务保持单数据源,能显著减少错误面。
服务间要查 DM 数据,更推荐通过 OpenAPI 方式调用:业务服务自己连 DM 后,把结果封装成 JSON 接口,其他服务通过 Feign 调用。而不是让所有服务都配一份 DM 数据源,那样既浪费连接,又难以维护。
不过有一种例外情况:你有一个公共服务 A 被多个服务通过 Feign 调用,而这个服务内部可能需要同时访问 MySQL 和 DM。这种设计尽量把 DM 访问收敛在少数几个 Service 中,多余的数据访问不应该铺到整个客户端 API 层。
5. 踩坑记录与问题排查实录
5.1 驱动程序找不到或连接不上的排查思路
最常见的就是启动或者执行时抛出Cannot load driver class: dm.jdbc.driver.DmDriver。这种问题十有八九不是代码问题,而是依赖打进了错误模块,或者根本没打包进最终可执行 JAR。
排查步骤:
- 确认目标服务中的
pom.xml已经声明达梦驱动依赖。 - 检查
mvn dependency:tree输出,确认依赖真的存在于当前模块依赖树中。 - 查看打出的 JAR 包内容:
jar tf 服务名.jar | grep dm.jdbc。看不到就说明依赖没打进包,检查spring-boot-maven-plugin的打包配置是否有 exclude 把该依赖排除了。 - 确认驱动类名大小写。虽然类加载通常对大小写敏感,但你在配置里写错大小写,会直接导致加载失败。
- 确认驱动版本和服务端版本匹配。DM8 的驱动通常向后兼容性还可以,但如果服务端是极老版本,新驱动可能连不上。
我遇到过一种特别隐蔽的情况:驱动确实被打进了包,但ruoyi-common-datasource里面对 JDBC 驱动做了 enable 或 disable 的开关配置,把某些驱动类给过滤了。这种应用级过滤在日志中往往没有任何显式报错,只是静默地找不到驱动。解决办法是全局搜索driver相关配置,看看是否被自定义代码影响了。
5.2 @DS 不生效与“切换串库”的隐蔽原因
@DS注解不生效,但项目又能正常启动,这种问题非常让人头疼。根据我实际经验,原因集中在以下几个方面。
首先,检查方法调用是否走了代理。@DS是基于 AOP 代理实现的,当你在同一个类的内部方法里调用另一个带注解方法时,比如:
public void outer() { this.inner(); // 不会走代理 } @DS("dm") public void inner() { }this.inner()不会触发注解,数据源不会切换。正确做法是把inner()挪到另一个类,或者注入自身的 Spring 代理对象。
其次,@DS和@Transactional同时存在时,上面 4.2 节已经说过顺序问题。事务一旦开启,连接已经绑定到线程,再切换数据源就晚了。
再次,检查 dynamic-datasource 自动配置是否被若依自己的数据源配置覆盖了。若依有些版本会自定义DruidConfig或DataSourceConfig,如果这个配置类用@Primary强制指定了某个数据源,那 dynamic-datasource 的路由机制可能根本没启用。这时候需要对比启动日志中加载的DataSource类型,如果看到的是DruidDataSource而不是DynamicRoutingDataSource,那说明自动配置被挤掉了。
解决办法是在启动类上或者配置类中显式排除若依原有的数据源自动配置:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})但这样做要小心:如果整个启动类排除了 DataSourceAutoConfiguration,而你的服务又是纯数据访问服务,可能连主库都连不上。所以这个排除动作不能照抄,必须先确认你的若依版本到底有没有自定义数据源配置类。
5.3 分页插件与达梦 SQL 方言冲突
现象很典型:同样的查询,在 MySQL 数据源正常,切到 DM 之后,分页 SQL 变成奇怪的形式并在数据库中报错,甚至直接把 PageHelper 的limit拼到 DM 不认识的语法上。
原因是 PageHelper 有方言自动识别机制,但不同版本对达梦的识别程度不一样。如果识别错了,它会当成 MySQL 方言处理,最终生成LIMIT ?, ?,而 DM 在 Oracle 兼容模式下不认这种写法。
处理方法:
- 在配置中显式指定
helper-dialect: oracle,让 PageHelper 用 Oracle 方言生成分页 SQL。 - 如果还是不认,单独给 DM 查询做分页时不要用 PageHelper 的
startPage,改用手动分页,查询前根据自己的分页参数拼好ROWNUM或OFFSET FETCH子句。 - 检查项目中是否有多个插件拦截器互相干扰。若依里如果同时存在 MyBatis 拦截器、数据权限拦截器和 PageHelper,拦截顺序是会影响最终 SQL 的。顺序不对时,PageHelper 生成的 count SQL 或分页 SQL 就可能被后续拦截器二次改坏。
我曾经在 DM 数据源上遇到SQL 执行异常: 第1行 [LIMIT] 附近出现错误,排查到最后发现竟然是数据权限拦截器在分页 SQL 基础上又拼了一段条件,两者组合后导致 DM 的 SQL 解析器崩了。换成 MySQL 时因为兼容性好没暴露,到了 DM 才原形毕露。
5.4 事务、并发下的连接池耗尽问题
多数据源下的连接池耗尽,不一定是“连接数配大了就行”这么简单。
我遇到过一种情况:某个服务既连 MySQL 又连 DM,在使用手动push切换数据源时,因为最后一个poll没执行,导致线程被污染。后续每次请求都绑定到 DM 数据源,大量本应走 MySQL 的请求全部挤在 DM 的小连接池上,而 MySQL 连接池几乎空转,DM 连接池却被打满,接口整体变慢甚至报wait connection timeout。
这个问题的排查思路:
- 看监控,哪个数据源的
activeCount异常高。 - 在日志中增加数据源切换的日志,确认是否存在长时间未释放的
push。 - 全面搜索
DynamicDataSourceContextHolder.push的代码,检查poll是否都能在finally中执行。
还有一种高手踩的坑:@DS("dm")的方法在往多线程里扔任务。比如方法 A 切换了 DM,然后ExecutorService.submit开子线程去查数据。子线程用的是线程池里的其他线程,这些线程没有绑定 DM 数据源标记,于是子线程最终走了默认的 MySQL,查询逻辑全错。这种问题比“串库”更隐蔽,因为外层主线程的数据源看起来是切对了,结果却不正确。解决办法是不要在多线程内部依赖@DS,最好在子线程任务里显式调用或者重新注入对应的 Service。
5.5 高频问题速查表
| 现象 | 根因 | 解决建议 |
|---|---|---|
| 启动报找不到驱动类 | 依赖没引入或未打包 | 检查模块 pom 与最终 JAR 内容 |
| 连接 DM 成功但 SQL 找不到表 | URL 未指定 schema | 在连接 URL 上加schema参数 |
| @DS 注解不生效 | this 调用导致代理失效 | 拆类或注入代理对象 |
| @DS 下执行 MySQL Mapper 报错 | 事务边界未拆分 | 用 REQUIRES_NEW 拆事务 |
| 分页 SQL 在 DM 上语法错误 | PageHelper 方言识别错误 | 配置 helper-dialect 为 oracle 或手写分页 |
| DM 连接池被打满 | 多线程切换标记污染 | push/poll 必须配对,放 finally |
| 子线程查库怀疑数据源错 | 动态数据源只绑定当前线程 | 子线程内重新指定数据源 |
这张表基本上覆盖了我这次改造中遇到的大部分问题。
最后分享一点个人体会。多数据源引入以后,最考验的不是第一次配置成功,而是后续长期演进中能否保持清晰。我见过有的团队为了让一个查询方法少写几行代码,把数据源切换藏在各种中间层里,最后代码里到处是push,排查问题翻半天。我更推荐的模式是:连接 DM 的入口集中在一个独立的 Service 模块,对外只暴露封装好的查询方法,其他模块看到的是一个“数据服务”而不是“数据源”。这样即使以后 DM 老系统下线,你只需要改这个独立模块,删除数据源配置,其他业务代码基本不用动。
如果你正打算在若依微服务里接达梦,先把依赖版本对齐,再把事务边界想清楚,最后才动手改业务代码。上面这些坑提前避过去,整个落地过程会顺很多。