
说实话RuoYi 适配达梦这个系列我原以为第一篇文章写完把驱动换上、连接串改一下再处理几个 SQL 大小写问题就算完事了。结果真把项目跑起来才发现适配工作远不止“连上”这么简单。第二篇我想重点聊聊那些藏得更深、但几乎每个迁移项目都会撞上的问题分页、函数差异、自增主键、定时任务还有开发工具和运维细节。如果你正在做 RuoYi 到达梦的迁移这篇文章应该能帮你少踩几个坑尤其是那些用 Navicat 或 DBeaver 连达梦、导入 dmp、或发现定时任务突然不执行的情况。1. 先把上一篇的“地基”重新检查一遍1.1 驱动、方言和连接配置这里还藏着两个暗坑第一篇里我们把驱动换成了dm.jdbc.driver.DmDriver连接串改成了jdbc:dm://ip:5236/SCHEMA_NAME当时验证基本查询没问题了。但实际跑起来我建议你回头再确认三个地方。第一是 MyBatis-Plus 的分页方言。如果你用的 RuoYi 版本是基于 MyBatis-Plus 做的分页那MybatisPlusInterceptor里一定有个PaginationInnerInterceptor这里默认的DbType可能是MYSQL。光改数据库连接串是不够的MyBatis-Plus 在生成分页 SQL 的时候不同数据库方言生成的语句差别很大。MySQL 方言生成的是LIMIT ?,?而达梦在非兼容模式下用的是自己的分页写法或依赖端偏移语法。强烈建议直接把DbType显式改为DbType.DM不要让它自动探测自动探测在多数据源或连接池懒加载模式下偶尔会拿到错误的方言。第二个是 Druid 连接池的wall防火墙。RuoYi 自带的是 Druid默认开启了防火墙过滤器。MySQL 环境下很多 SQL 写法是合法的但切到达梦后如果建表语句、批量插入或某些函数被wall拦截你会看到类似sql injection violation的报错。这种问题排查起来非常迷惑因为 SQL 本身完全没问题。处理办法是在application-druid.yml里把wall的noneBaseStatementAllow打开或者临时加filter.wall.enabledfalse验证一下。第三个是DruidDataSource的validationQuery。MySQL 环境下很多项目用的是SELECT 1放在达梦下当然没问题。但如果你用的是 Oracle 兼容模式并且习惯性写成SELECT 1 FROM DUAL达梦也支持问题不大。需要注意的反而是达梦连接超时后的自动重连行为建议把testWhileIdle和timeBetweenEvictionRunsMillis配上避免数据库端空闲连接回收后应用侧还在用死连接。后面我会专门讲会话超时这个问题这里先埋个伏笔。1.2 大小写、模式和建表习惯一定要统一规则达梦对大小写的处理逻辑是如果建表时字段不加双引号那么表名和字段名会统一转为大写存储如果你在 SQL 里写了小写表名不加双引号达梦会先把它转成大写再去匹配。这跟 MySQL 在 Linux 下的区分方式完全不同所以最容易出的问题就是“表明明存在却报找不到”。我见过一个典型场景RuoYi 自带的sys_user表在 MySQL 里是小写迁移到达梦后用迁移工具倒过去表名很可能变成了大写SYS_USER。但代码里 MyBatis 的 XML 写的是select * from sys_user达梦会把它转换成SELECT * FROM SYS_USER如果表名确实是大写反而能正常查到。真正出问题的是反向情况有人建表时用了双引号强制小写表名sys_user那 SQL 里不加双引号查sys_user就会报“无效的表名或视图名”。所以说迁移时定一个规则所有表名和字段名统一不带引号建表让达梦自动转大写SQL 层也统一用小写编写。这样在 MySQL 和达梦之间切换时SQL 基本可以通用。模式Schema这边也值得提一下。达梦的 Schema 大致对应其他数据库里的“用户”你连数据库时如果不指定 Schema默认就是登录用户自己的 Schema。RuoYi 多数据源、代码生成这些功能里如果有地方硬编码了mysql库名或者在表名前加了库名前缀要统一去掉或改成达梦的 Schema 名。另一个常见问题是dm命令行的disql里通过SET SCHEMA 模式名来切换当前模式连接串里也可以用jdbc:dm://ip:5236/模式名的方式指定但这跟连接database的概念不太一样启用后要注意连接池里多个请求是否共享同一个 Schema 状态。2. 分页与 SQL 兼容改造最容易翻车的地方都在这2.1 分页插件切换到达梦方言看似简单但值得多验证几轮我在多个项目里的实操经验是RuoYi 的列表页翻页一定要重点测。原因很简单列表页面都依赖分页插件自动生成 count 查询和分页语句一旦方言配置不对或 SQL 里有特殊函数第一页可能正常第二页就报错或者总条数统计完全错误。如果你用的是 PageHelper配置里有个helperDialect参数默认可能从 URL 自动识别但建议手动写上dm。PageHelper 对达梦的识别在早期版本并不完善升级到比较新的版本才稳。如果你是 MyBatis-Plus就按我前面说的设置DbType.DM。在我实际测下来达梦在兼容 MySQL 模式下对LIMIT ?,?的接受度不错但如果你初始化数据库时选了 Oracle 兼容模式那分页 SQL 就必须是达梦的LIMIT ? OFFSET ?或 Oracle 的ROWNUM写法这时候 MyBatis-Plus 方言配置错了整个分页都跑不起来。还有一类容易被忽略的坑是分页 count 查询和列表查询里如果用了ORDER BY子查询或开窗函数达梦的语法要求更严格。比如 MySQL 中ORDER BY (SELECT ...)这种写法达梦可能不认。建议遇到统计条数不对或 count 报错的时候去数据库里直接把 count SQL 拉出来跑一遍重点看有没有别名冲突、字段歧义和子查询排序。2.2 高频 SQL 函数的达梦替代清单这一节我给一份我在适配过程中整理的函数替换表不敢说覆盖全部但 RuoYi 里常用的基本都在了。MySQL 写法达梦推荐写法说明IFNULL(a, b)NVL(a, b)或IFNULL(a, b)达梦兼容 MySQL 模式下两者都可用但 Oracle 兼容模式必须用NVLIF(cond, a, b)CASE WHEN cond THEN a ELSE b END达梦没有 IF 函数统一改 CASE WHEN 最保险DATE_FORMAT(now(), %Y-%m-%d)TO_CHAR(SYSDATE, YYYY-MM-DD)格式符不同注意%H要换成HH24NOW()/CURRENT_TIMESTAMPSYSDATE或CURRENT_TIMESTAMP达梦都支持SYSDATE最通用GROUP_CONCAT(name)LISTAGG(name, ,)或WM_CONCAT(name)MySQL 独有的达梦建议用LISTAGG配合WITHIN GROUP (ORDER BY ...)控制顺序CONCAT(a, b)CONCAT(a, b)或a || b推荐直接用 SUBSTRING_INDEX(str, ,, 1)用SUBSTRINSTR组合实现没有直接等价函数需要改写REPLACE(str, a, b)REPLACE(str, a, b)可用注意有些版本要求参数非空UUID()SYS_GUID()返回格式不同长度要处理FIND_IN_SET(a, b)自定义函数或用INSTR模拟这个比较麻烦建议看业务逻辑改写这份表看起来简单但实际替换的时候最大的陷阱是GROUP_CONCAT。RuoYi 的通知公告、部门树、角色列表里都有子查询拼接多个名称的场景。如果换成LISTAGG务必确认分组字段是否为空LISTAGG在遇到NULL值时结果会跳过空值但达梦对分组内数据量特别大时还可能报“字符串连接结果超长”需要在函数里限制长度或用ON OVERFLOW子句达梦新版本支持。这里没有银弹只能按字段长度裁剪后拼接。2.3 反引号、布尔字段和保留字这些小问题最磨人MySQL 里写字段名带反引号是常识但达梦不认反引号只认双引号。RuoYi 的 XML 里如果有select \id from ... 这种写法到达梦里直接报语法错误。我的处理办法是全局搜索 XML 里的反引号批量删掉然后逐个确认没有用到达梦保留字。达梦保留字需要特别留意比如COMMENT、LEVEL、TYPE、USER、ORDER这些。如果某张表的字段名碰巧叫commentSQL 里直接写comment会炸必须加双引号改成COMMENT才能过。这类问题在 SQL 里是偶发的建议建表后跑一遍 RuoYi 所有的查询接口利用报错来反向定位。布尔字段也值得一说。MySQL 里的tinyint(1)在达梦里可以映射为TINYINT或SMALLINT但如果你用了 MyBatis 的BooleanTypeHandler建议在达梦中检查字段返回的类型是不是被识别成了Integer。如果出现Cause: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Boolean就不要偷懒在实体字段上显式加TableField(typeHandler ...)或者在 XML 里做类型转换。这类问题在代码生成器生成的实体上尤其常见因为生成时读到的列类型映射关系跟 MySQL 不一样。3. 自增主键、序列与自定义函数适配3.1 IDENTITY 与 SEQUENCE怎么选更省事RuoYi 的多数表主键用的是自增AUTO_INCREMENT。在达梦里有两种做法一种是建表时直接定义IDENTITY(1,1)把自增列作为主键这最接近 MySQL 的习惯也能少改代码另一种是为每张表建序列CREATE SEQUENCE插入时用NEXTVAL取值。我在实际项目里推荐优先用第一种IDENTITY原因很简单RuoYi 大量用了 MyBatis-Plus 的TableId(type IdType.AUTO)插入后需要自动回填主键IDENTITY在达梦里配合 JDBC 驱动可以很好地回填。用序列的话你得把实体主键策略改成INPUT并且自己调用序列改动的面积大得多。但IDENTITY也有一个烦人的限制如果业务代码里想显式给主键插入一个指定值默认是不允许的。MySQL 里往自增列写 0 或具体值都很随意达梦会直接报错。解决办法是执行SET IDENTITY_INSERT 表名 ON但这是个会话级设置用完要记得关。另外迁移历史数据的时候老表里有大量指定 ID 的 INSERT这很让人痛苦我当时的做法是数据迁移阶段用SET IDENTITY_INSERT ON在同一个会话里完成应用上线后不再手工插入避免每个连接都要设置。3.2 以“生成首拼码”为例说说达梦自定义函数的写法热词里有个“达梦数据库生成首拼码函数”这个需求在 RuoYi 的一些进销存、CRM 项目里很常见。你需要在数据库层根据汉字生成拼音首字母简码比如“客户名称”生成KHMC。MySQL 里有各种封装好的方案达梦这边如果不装额外的扩展包最稳妥的就是自己写一个自定义函数。我提供一个可在达梦上直接执行的简化版基于 Oracle 兼容模式CREATE OR REPLACE FUNCTION GET_PYBM(str IN VARCHAR2) RETURN VARCHAR2 AS V_RETURN VARCHAR2(4000); V_CHAR VARCHAR2(2); BEGIN V_RETURN : ; FOR i IN 1..LENGTH(str) LOOP V_CHAR : SUBSTR(str, i, 1); SELECT MAX(CASE WHEN V_CHAR BETWEEN A AND Z THEN V_CHAR WHEN V_CHAR BETWEEN a AND z THEN UPPER(V_CHAR) WHEN V_CHAR BETWEEN 0 AND 9 THEN V_CHAR WHEN V_CHAR 吖 AND V_CHAR 驁 THEN A WHEN V_CHAR 八 AND V_CHAR 簿 THEN B -- 这里按拼音区间继续补全 C-Z ELSE A END) INTO V_RETURN FROM DUAL; -- 简化实现仅演示逐字拼接逻辑 END LOOP; RETURN V_RETURN; END;实际上不能这么简单处理因为达梦的字符比较和区间判断在不同字符集下行为有差异。更可靠的方式是建立一个拼音码表把常用汉字和首字母映射关系放到一张临时表里在函数里去查。网上也有不少现成的五笔码表或拼音码表脚本导入后做一个函数查询。这里我想强调的核心点是不要在函数里做复杂的区间判断太容易踩字符集和排序规则的坑用码表驱动是最稳的。函数本身写好后要注意达梦的函数默认是DETERMINISTIC还是不启用如果 SQL 里大量调用性能不够理想时可以考虑把计算结果落到实体字段上插入或更新时由业务代码计算好再存减少数据库实时计算的负担。4. 定时任务不执行和代码生成适配4.1 Quartz 建表脚本改造和“任务不执行”的真正原因RuoYi 自带的定时任务功能基于 Quartz。MySQL 环境下一套标准脚本建好表就能跑但切到达梦后很多人会遇到“任务控制台能看到手动执行也能跑但定时就是不触发”或者“日志里报一堆表不存在”的问题。先说表不存在的根源Quartz 官方没有直接提供达梦的脚本网上能找到的很多是 Oracle 版本脚本改的。RuoYi 的sql/quartz.sql是 MySQL 方言里面有TINYINT、BLOB、LONGTEXT之类拿到达梦管理工具里执行会报错。所以你需要一份达梦能用的 Quartz 表脚本或者用 Oracle 版脚本再把字段类型微调成达梦的VARCHAR2、CLOB、BLOB。再说不触发的常见原因。我遇到过几次最后定位到几个点qrtz_job_details表的JOB_DATA是BLOB类型存储的是持久化后的 job 数据。如果驱动在写入时用了 MySQL 特有的大对象处理方式数据读出来反序列化失败导致调度器后台一直刷错误日志任务永远不触发。解决方法是把JOB_DATA列改成CLOB或者确认达梦 JDBC 驱动和 Quartz 版本兼容。另外注意qrtz_trigger里TRIGGER_NAME和JOB_NAME等字段的定长设置如果 VARCHAR 长度不够也会在插入的时候出问题。集群模式下org.quartz.scheduler.instanceId配置为AUTO生成时会依赖主机名和网卡信息。在某些云服务器上主机名过长或包含特殊字符达梦存储INSTANCE_ID时可能截断或转换失败导致调度器无法注册。建议手写成固定 IDorg.quartz.scheduler.instanceId RUIYI_QUARTZ_01。org.quartz.jobStore.isClustered如果为true但没有把所有节点的时间做同步或者达梦的事务隔离级别设置不当抢锁逻辑会异常。可以先把集群关掉false在单节点环境确认能跑通后再开启。4.2 代码生成器读写表结构达梦下的几个坑RuoYi 的代码生成器要读取表结构、列注释、索引信息。这些查询在 MySQL 里主要走information_schema。达梦提供了名为USER_TAB_COLUMNS、ALL_TABLES、ALL_COL_COMMENTS这些系统视图如果你直接把 RuoYi 里的select * from information_schema.COLUMNS where table_schema ...拿过去跑会发现 columns 集合为空或表名对不上。我的做法是在代码生成器的数据源配置上单独写一套达梦的元数据查询 SQL。具体到 RuoYi 的GenTableServiceImpl里可以扩展一个SELECT适配分支用达梦的系统视图查出表名、列名、字段类型、注释、主键等。这套 SQL 写起来不难但要注意达梦的列注释不像 MySQL 那样直接在列定义里带COMMENT而是通过COMMENT ON COLUMN 表名.列名 IS ...维护的。查询注释的时候要左连接ALL_COL_COMMENTS或USER_COL_COMMENTS。很多人在这一步会被“查出来没有注释”坑到不是数据真没注释而是查错了视图或关联字段。另外代码生成器里有个“根据表名查询列表”的前端页面如果表名筛选条件用了LIKE %关键词%达梦下同样要注意关键字USER、COMMENT的引号处理。这部分的改动不算大但牵涉到生成器本身的逻辑分支建议单独留出一个达梦适配的 Service 实现别跟 MySQL 逻辑揉得太紧。5. 管理工具、备份还原与连接保活5.1 开发工具连达梦让 Navicat、DBeaver 和 DM 管理工具各就各位日常开发中用 Navicat 连达梦已经比较成熟了连接时驱动选择达梦填主机、端口、用户名、密码即可。DBeaver 连达梦需要手动添加驱动把达梦安装目录里的DmJdbcDriver18.jar引用进去然后新建连接时选择“达梦”URL 模板里填jdbc:dm://ip:5236。这里有个容易踩的坑如果你直接拿 MySQL 的驱动包连达梦连接会失败必须用达梦自带的 JDBC 驱动且版本最好和数据库实例版本保持一致。DBeaver 社区版在识别大数据类型和视图时偶尔会卡我一般只是拿它做查询建表、导数据还是用 DM 管理工具更省心。有人会问“达梦 DM 管理工具没有对象导航栏怎么办”。这种情况多出现在首次打开某个连接后窗口布局被重置。在菜单栏“窗口”里找到“显示视图”或“导航”把对象导航栏调出来就行。如果调不出来可能是连接没建立成功或者用户权限看不到任何对象。解决思路是先确认能查到系统视图和普通表再看是不是被授权限制了。5.2 备份还原、会话超时和授权到期运维要提前做准备热词里提到“达梦数据库备份与管理工具还原 dmp 文件”。达梦的dmp文件不像 Oracle 那种用imp导入达梦有自己的dexp和dimp工具。命令行示例dimp USERIDSYSDBA/SYSDBAlocalhost:5236 FILE/data/back.dmp FULLY。如果在 DM 管理工具里还原一般会提供图形化的导入功能。这里最需要注意的是dmp文件的导出字符集和导入字符集的一致性如果源库是 GBK目标库是 UTF-8导入后中文数据大概率乱码。建议导出前先确认两边的字符集配置统一后再迁移。“达梦数据库突然连不上”是另一个高频问题。除了服务没启动、端口被防火墙拦截之外还有一个很隐蔽的原因是达梦的授权到期了。达梦数据库在评估授权过期后数据库服务可能拒绝新建连接或者报“License expired”。这种问题不要一开始就往代码方向查先用 DM 管理工具或disql本机连一下确认服务能不能连上。如果能连上再查应用日志如果本机都连不上多半是服务状态或授权问题。授权到期后需要联系商务续期重启服务后才能恢复。再说“达梦数据库未设置会话超时时间”的问题。达梦默认的会话空闲超时时间如果不配置连接可能在长时间空闲后被服务端回收而连接池不知道下次请求就报通信异常。建议在数据库参数里显式设置会话超时时间比如ALTER SYSTEM SET SESSION_TIMEOUT 00 表示不超时同时把连接池这边的testWhileIdle和timeBetweenEvictionRunsMillis调好。两边一起处理才能避免“连接池里有死连接”这种玄学问题。另外达梦在 Windows 和 Linux 下的服务管理命令不太一样Windows 可以用图形工具Linux 上要用DmServiceDMSERVER start/stop这类脚本别搞混。6. 常见问题与排查速查表我根据这次适配的整个过程整理了一个速查表适合打印出来贴在工位上。症状可能原因处理建议启动报“无效的表名或视图名”表名大小写不匹配检查建表是否使用了双引号强制大小写SQL 统一小写建表不带引号分页查询第二页报错分页方言仍是 MySQLMyBatis-Plus 设置DbType.DMPageHelper 设置helperDialectdmLIMIT语法异常达梦处于 Oracle 兼容模式改写分页 SQL 或用方言插件自动生成达梦兼容语句GROUP_CONCAT不存在MySQL 专有函数替换成LISTAGG注意空值和超长定时任务不触发Quartz 表类型不兼容或集群 ID 异常换用达梦版 quartz 脚本instanceId手动指定关闭集群测试ClassCastException: Integer cannot be cast to Boolean布尔字段类型映射错误实体上加类型处理器或调整字段类型映射Navicat 连接失败驱动版本或端口问题确认使用达梦 JDBC 驱动端口默认 5236DM 管理工具无对象导航窗口布局或权限问题在窗口菜单找回视图确认账号能看到表数据库突然连不上服务未启动 / 授权过期 / 防火墙先用 disql 本机验证再查授权和端口导入 dmp 后中文乱码导入导出字符集不一致统一源库和目标库字符集后再迁移空闲一段时间后应用报连接异常数据库会话超时 连接池死连接设置SESSION_TIMEOUT配合 Druid 空闲检测这一版适配做完之后我最大的感觉是达梦本质上兼容了多种数据库的方言所以没有一套固定规则能套用在所有项目上关键是要搞清楚你初始化实例时选了哪种兼容模式、应用层用的是哪套 ORM 和分页插件。很多问题看起来是代码没写好实际是环境配置和数据库特性在底层打架。再说一个我的个人体会做这种数据库适配一定不要一次性把所有 SQL 都改完再启动风险太大。更稳的方式是先把 RuoYi 跑起来登录、系统管理、权限管理这些核心模块一个一个点过去每点一个模块就查一遍对应模块的 SQL 日志发现不对的当场在数据库里验证改写。这样虽然花的时间长但每一项改动都能定位到具体业务场景后续排障也容易得多。达梦的兼容性问题大多有规律可循多备一份函数对照表和系统视图说明遇到问题按表索骥能省下不少连蒙带猜的时间。