ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RuoYi若依框架适配达梦数据库:SQL迁移与主键分页改造实践

RuoYi若依框架适配达梦数据库:SQL迁移与主键分页改造实践 前一篇写完了达梦数据库在Windows下的安装、DM管理工具的基础操作以及Navicat连达梦的那点事儿。这一篇就进入正题RuoYi若依这个项目在适配达梦数据库时代码层和数据层到底要动哪些东西。这篇内容差不多就是我实际把整个后端跑通、前端页面能正常出数的一个完整记录里面的坑和解决办法都是自己一步步踩出来的。先说结论RuoYi默认基于MySQL设计但它的SQL写得还算规整没有太依赖复杂的MySQL特性所以迁移到达梦没有想象中那么可怕。但“不算可怕”不等于“直接能用”中间有几处硬骨头比如分页、主键策略、系统函数差异、以及达梦自己的模式管理这些地方必须懂原理才能改得对。这篇记录就地取材以RuoYi前后端分离版为例子讲清楚每一处适配的原因和方法以及我实测过的稳定写法。文章里的SQL示例、配置片段、排错思路都能直接抄作业。1. 迁移前先想明白达梦和MySQL到底差在哪1.1 RuoYi为什么默认绑定MySQLRuoYi后端使用MyBatis作为持久层框架本身对数据库类型并不敏感真正的敏感点在于SQL写法和底层JDBC行为。RuoYi默认的SQL脚本是MySQL方言里面用到AUTO_INCREMENT自增列、ENGINEInnoDB表选项、反引号包裹字段名等这些都是MySQL的“习惯动作”。很多人在迁移时直接拿MySQL脚本去达梦里执行结果一片红。原因不是达梦不认SQL而是SQL方言不同。达梦语法整体偏向Oracle风格同时又能设置兼容模式模拟部分MySQL语法但兼容不等于完全一致。项目的长期维护稳定还是建议在SQL层面做真正的兼容改造而不是依赖数据库的兼容开关。我当时的决策是不开启达梦的MySQL兼容模式直接按Oracle风格改SQL。思路很简单达梦对Oracle的兼容性比MySQL兼容性成熟得多网上能查到的经验也多踩坑成本低。1.2 达梦的模式Schema概念必须先搞懂MySQL里“数据库”和“表”是两层概念比如ruoyi库下面有sys_user表。达梦里多了一个“模式”的概念一个用户可以对应一个模式默认情况下用户名就是模式名。比如用SYSDBA登录默认操作的模式就是SYSDBA。这带来的直接影响是所有表名、字段名在SQL里都带上了模式前缀像SYSDBA.SYS_USER。RuoYi的Mapper SQL如果写成select * from sys_user在达梦里就需要保证当前连接默认用的模式就是该表所在的模式否则会报“无效的表名”。我的做法是建一个专用业务用户比如RUOYI让所有RuoYi表都建在RUOYI模式下然后在JDBC连接串里指定schema参数这样写SQL时就不用每个表都加前缀了。关于大小写又是另一个坑。达梦默认对不带引号的标识符会转成大写建表语句里写的sys_user最终会变成SYS_USER。如果迁移时原MySQL库的表名是小写导出脚本时建议直接统一改成大写或者建表时全部带双引号保留小写。最省事的办法是全部用大写避免后面SQL里大小写不一致导致的“无效列名”问题。1.3 把适配工作拆成四个任务把整个迁移拆成四个部分逐个击破出问题也好定位依赖与数据源配置引入达梦驱动、修改JDBC URL、调整Druid连接池参数。表结构与主键策略把MySQL建表脚本改造成达梦语法重点处理自增列和序列。SQL方言适配系统内置的Mapper XML里分页、函数、关键字等写法改写。功能回归验证登录、用户管理、角色权限、代码生成、定时任务等模块逐个跑通。这四个任务没有严格的先后顺序但建议按顺序做前一步不跑通后一步查问题会很痛苦。2. 工程依赖与数据源改造先把环境跑通再谈其他2.1 引入达梦JDBC驱动并处理依赖冲突RuoYi的pom.xml里默认依赖了MySQL驱动改成达梦需要先把这个依赖排除掉再加到达梦驱动。达梦官方驱动包有几个版本我用的是DmJdbcDriver18对应JDK 1.8及以上。依赖坐标参考dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.1.193/version /dependency官方驱动并没有完全推送到公共Maven中央仓库我是在达梦安装目录的drivers/jdbc下找到的DmJdbcDriver18.jar然后手动install到本地仓库mvn install:install-file -DfileDmJdbcDriver18.jar -DgroupIdcom.dameng -DartifactIdDmJdbcDriver18 -Dversion8.1.1.193 -Dpackagingjar这里有个容易忽略的点如果项目里还依赖了其他ORM或连接池组件要注意排除掉传递依赖里的MySQL驱动否则运行时可能会因为classpath里同时存在两个驱动而出现奇怪的连接行为。稳妥起见直接在RuoYi父级pom.xml的依赖管理里锁定使用达梦驱动并排除mysql-connector-java。2.2 修改Druid数据源配置RuoYi的数据库配置集中在application-druid.yml里。针对达梦核心是改三处驱动类名、连接URL、连接池相关参数。spring: datasource: druid: master: driverClassName: dm.jdbc.driver.DmDriver url: jdbc:dm://127.0.0.1:5236?schemaRUOYI username: RUOYI password: your_password initialSize: 5 minIdle: 5 maxActive: 20 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false几个参数的原因说明一下达梦默认端口是5236安装时如果没改过就用这个。URL后加的schemaRUOYI让JDBC连接默认落在RUOYI模式下后面的SQL不用频繁加模式前缀。validationQuery写成SELECT 1 FROM DUAL。达梦支持Oracle风格的DUAL表但MySQL写法是SELECT 1不带表名Druid默认检测数据库类型时可能识别有偏差。如果还有问题可以把validationQuery直接留空让Druid用ping方式检测也能减少一次查询开销。2.3 配置文件中分页插件方言要同步改RuoYi使用了PageHelper做分页。PageHelper需要知道当前数据库方言否则它生成的分页SQL可能是MySQL的LIMIT语法而达梦在非兼容模式下根本不吃这一套。我直接在application.yml里增加了PageHelper配置pagehelper: helper-dialect: dm reasonable: true support-methods-arguments: truehelper-dialect设为dm后PageHelper会按达梦语法生成分页。达梦对标准分页的支持挺有意思其实它支持FETCH FIRST N ROWS ONLY这种SQL标准写法。设置正确后PageHelper会把LIMIT转换成适合达梦的写法显示效果和MySQL完全一致前端的PageHelper参数也无需调整。如果这里不设置最典型的报错是在翻页接口上出现类似“关键字LIMIT附近出现语法错误”这样的提示。很多群友跑来问分页报错多半就是漏了这一步。2.4 验证连接是否真实可用配置改完后别急着启动整个应用我习惯先写一个最小的测试来验证数据源SpringBootTest public class DmConnectionTest { Autowired private DataSource dataSource; Test public void testConnection() throws Exception { Connection conn dataSource.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT USER(), SYSDATE FROM DUAL); while (rs.next()) { System.out.println(rs.getString(1)); System.out.println(rs.getString(2)); } rs.close(); stmt.close(); conn.close(); } }达梦里SYSDATE返回当前时间这个查询能跑通就说明连接、驱动、用户权限都没问题。这一步能过滤掉至少一半的低级问题后面改SQL心态会稳很多。3. 表结构迁移与自增主键、序列处理3.1 把MySQL脚本改造成达梦语法RuoYi源码的sql目录下提供了MySQL版脚本。直接执行会碰到几个报错点ENGINEInnoDB带表选项达梦不认。AUTO_INCREMENT列属性达梦不直接支持。反引号包裹的字段名达梦要用双引号才能达到同样效果。COMMENT作为列注释的语法有差异。datetime、text、tinyint等类型虽然部分兼容但建议按达梦习惯调整。我实际操作时没有用工具自动转换而是手工改写了一遍核心表。原因很简单RuoYi的表就几十张手工改虽然枯燥但保险自动工具转换出来的脚本往往带着一堆冗余和兼容性问题。以sys_user表为例原来的MySQL建表语句关键片段CREATE TABLE sys_user ( user_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 用户ID, user_name varchar(30) NOT NULL COMMENT 用户账号, nick_name varchar(30) NOT NULL COMMENT 用户昵称, ... PRIMARY KEY (user_id) ) ENGINEInnoDB AUTO_INCREMENT1 COMMENT 用户信息表;改成达梦可执行的版本CREATE TABLE SYS_USER ( USER_ID BIGINT IDENTITY(1,1) NOT NULL, USER_NAME VARCHAR(30) NOT NULL, NICK_NAME VARCHAR(30) NOT NULL, ... PRIMARY KEY (USER_ID) ); COMMENT ON TABLE SYS_USER IS 用户信息表; COMMENT ON COLUMN SYS_USER.USER_ID IS 用户ID; COMMENT ON COLUMN SYS_USER.USER_NAME IS 用户账号; COMMENT ON COLUMN SYS_USER.NICK_NAME IS 用户昵称;关于字段类型一个小提示不要把MySQL的datetime原样搬到达梦里建议用TIMESTAMP达梦对TIMESTAMP支持更好而且RuoYi的create_time、update_time这些字段在代码里对应的是Date类型TIMESTAMP接起来没有类型转换问题。字符串类型里varchar没问题但超过一定长度比如描述类的长文本字段注意用CLOB不要用超长VARCHAR否则后续排序或去重可能报错。3.2 用户表主键IDENTITY还是SEQUENCE自增主键是迁移中躲不开的问题。达梦支持两种方案第一种是用IDENTITY自增列建表时直接声明插入时不写主键字段数据库自动生成。这种方案最接近MySQL的AUTO_INCREMENT改造成本低。第二种是用序列加触发器类似Oracle的经典玩法。先创建序列再建一个BEFORE INSERT触发器插入时从序列取下一个值填到主键里。我最初选了IDENTITY方案因为RuoYi的Mapper XML里新增用户的insert语句通常不包含user_id字段MyBatis配置了useGeneratedKeystrue和keyPropertyuserId希望在插入后回填主键。这个机制本质上依赖JDBC驱动对getGeneratedKeys的支持而我实测下来达梦驱动对这个功能的支持不完整。表现是插入用户后返回的自增ID有时候为0甚至直接报错。这会导致后续比如用户角色关联数据的插入拿不到正确的用户ID。我最后的解决办法是放弃IDENTITY和getGeneratedKeys回填改用序列显式赋值的方式。具体来说先创建序列CREATE SEQUENCE SEQ_SYS_USER START WITH 1 INCREMENT BY 1 NOCACHE;然后把sys_user表结构改成不依赖IDENTITY主键字段建为普通BIGINT。再修改MyBatis的insert语句显式写好主键字段的取值insert idinsertUser parameterTypeSysUser selectKey keyPropertyuserId resultTypelong orderBEFORE SELECT SEQ_SYS_USER.NEXTVAL FROM DUAL /selectKey insert into sys_user( user_id, user_name, ... ) values ( #{userId}, #{userName}, ... ) /insertselectKey在插入前先从序列取号再把值赋给userId最终插入SQL里带上具体的ID。这种方式绕开了JDBC对getGeneratedKeys的兼容依赖兼容性和稳定性都有保证。代价是每个主键都需要建序列RuoYi里核心表几十张序列也建几十个但实际不复杂一条SQL建一个序列复制执行就行。3.3 迁移后的表结构校验表结构全部建完之后建议用DM管理工具做一轮检查重点看三样东西每个表是否有主键。字段注释是否齐全。RuoYi的代码生成器依赖表的注释生成页面显示名注释缺失会导致生成的前端代码上所有字段名称都是英文体验很差。每个核心表是否都有配套的序列。我见过不少项目迁完表后能跑但一到代码生成模块就出问题十有八九是表注释没保留或者是类型映射让代码生成器无法识别。这块宁可多花十分钟检查后面省出来的时间不止十分钟。4. SQL兼容性改造MyBatis XML里那些不得不改的写法4.1 分页SQL从LIMIT改成ROWNUM或FETCH FIRST除了PageHelper之外RuoYi里有部分自带的Mapper SQL手写了分页比如代码生成模块里的selectDbTableList等查询。这些SQL中如果包含MySQL特有的LIMIT语法PageHelper救不了需要手工改掉。达梦支持两种分页写法第一种是Oracle经典的ROWNUM方式SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( SELECT * FROM SYS_USER ORDER BY USER_ID ) TMP WHERE ROWNUM ? ) WHERE ROW_ID ?第二种是SQL标准的OFFSET/FETCH方式SELECT * FROM SYS_USER ORDER BY USER_ID OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY;RuoYi的Mapper XML里没有过多手写分页SQL所以主战场还是PageHelper。如果遇到个别手写的建议优先用OFFSET/FETCH写法简洁且不容易出现ROWNUM嵌套带来的边界数据重复问题。4.2 常用函数的替换清单RuoYi内置SQL里用到了一些MySQL函数这些到达梦都不直接支持。下面是我实际替换过的清单MySQL写法达梦等价写法说明IFNULL(a, b)NVL(a, b)空值替换DATE_FORMAT(now(), %Y-%m-%d)TO_CHAR(SYSDATE, YYYY-MM-DD)日期格式化NOW()SYSDATE当前时间GROUP_CONCAT(name)LISTAGG(name, ,)字符串聚合达梦用LISTAGGCONCAT(a, b, c)a || b || c字符串拼接CONCAT只支持两个参数时慎用IF(expr, t, f)CASE WHEN expr THEN t ELSE f END条件表达式UUID()SYS_GUID()生成UUIDSUBSTRING_INDEX(str, ,, n)需要自己写或用INSTRSUBSTR组合字符串拆分其中最容易踩坑的是GROUP_CONCAT。RuoYi有些查询会拼接多个用户名MySQL里GROUP_CONCAT很顺手但达梦必须改为LISTAGG。注意LISTAGG在数据量大的时候如果拼接结果超长会报错所以聚合前要对字段长度心里有数必要时加ON OVERFLOW TRUNCATE之类的容错写法达梦版本不同写法略有差异低版本可以直接用SUBSTR截断处理。4.3 关键字冲突和一些隐蔽的语法坑MySQL对关键字的容忍度比达梦高不少。迁移后我遇到的第一个关键字冲突就是COMMENT。RuoYi的字典数据表里有个字段专门存备注MySQL里叫comment没什么问题但达梦里COMMENT是建表语句里的关键字SELECT和INSERT时直接写会报错。我的处理方式有两个选择一是给字段加双引号二是直接改名。我建议直接改物理表字段名把comment改成remark或其他不冲突的名字同时把对应的实体类字段、Mapper映射、前端表格列同步调整。用双引号虽然能绕过关键字限制但后面代码生成器、动态SQL拼装时容易因为引号问题产生新坑不划算。除了COMMENT我还遇到过LEVEL、SIZE、TYPE等字段名在某些达梦版本中报错的情况。如果恰好这些词被用作列名建议统一加双引号规避或者改名稳妥为先。另一个隐蔽的坑是GROUP BY规则。MySQL默认ONLY_FULL_GROUP_BY没开的时候SELECT的字段可以比GROUP BY字段多达梦在某些模式下管得比较严会报“不是GROUP BY表达式”。这种SQL如果出现在RuoYi的自定义业务SQL里需要改成把非分组字段用聚合函数包住或者把字段加到GROUP BY里。4.4 批量插入的语法差异RuoYi里很多批量插入是通过MyBatis的foreach拼的类似insert idbatchInsert insert into sys_user_post(user_id, post_id) values foreach collectionlist itemitem separator, (#{item.userId}, #{item.postId}) /foreach /insert这种多行VALUES批量插入语法MySQL完全支持但达梦默认不支持这种简写方式。有两种改法第一种改成多张表UNION ALLINSERT INTO SYS_USER_POST(USER_ID, POST_ID) SELECT #{item.userId}, #{item.postId} FROM DUAL UNION ALL SELECT #{item.userId2}, #{item.postId2} FROM DUAL第二种是改用达梦的批量参数方式但MyBatis里写起来比较繁琐不适合大范围改造。我用的是第一种把foreach里的separator改成UNION ALL配合SELECT FROM DUAL。但这种方式有一个前提item.userId这些参数必须能正常取到值否则SQL拼接会出错。测试时重点看批量分配角色、批量导入用户这类功能。如果不想大改XML也可以考虑在达梦实例上开启兼容模式。但我的建议是不要为了省这点事开兼容开关因为兼容模式可能影响后续复杂SQL的性能和执行计划的稳定性与其依赖环境不如让SQL本身在两类数据库之间都“讲得通”。5. 实操中的典型报错与排查实录5.1 我遇到的报错速查表报错信息原因解决办法无效的列名大小写不匹配表结构统一大写SQL里别用反引号关键字“LIMIT”附近有语法错误SQL里残留MySQL分页写法检查PageHelper方言配置手写SQL改OFFSET/FETCH表中不存在该记录或列名无效模式前缀缺失JDBC URL加schema参数或SQL中加上模式名数据长度超出字段允许的最大长度VARCHAR长度定义过小或LISTAGG超长调大字段长度或用CLOB必要时截断无法转换为CLOB或BLOBCLOB类型参与DISTINCT/GROUP BY先转成VARCHAR再分组无效的关系名表名带了MySQL反引号反引号改为双引号或去掉identity 列不允许显式插入IDENTITY自增列被显式赋值改成序列显式取值方案getGeneratedKeys不支持驱动对自增主键回填兼容不完整使用selectKey预取序列值5.2 一个典型的排查例子登录接口500错误我迁移完第二天后端能启动但登录接口一调就报500。控制台错误指向selectUserByUserName这条SQL。一开始我以为是SQL里某个函数问题检查了IFNULL替换、NVL替换都没问题。后来把SQL单独复制到DM管理工具执行发现能跑通。这就奇怪了应用里执行报错工具里不报错。最后定位到问题是大小写敏感和连接用户环境不同。DM管理工具用SYSDBA登录默认模式是SYSDBA而应用连的是RUOYI用户。表是在RUOYI模式下建的但应用连接时有部分SQL访问的表没带模式前缀连接串里的schema参数虽然设了但某些语句因为大小写原因解析时没走预期路径导致找不到表。解决办法是把SQL里涉及到的所有表名统一成大写重新执行同时确认连接串的schema确实生效。这里想提醒一下连接串里schema参数不是万能的如果代码里显式写了其他模式名优先级以SQL里的为准。排查这类问题最直接的方式就是把MyBatis日志打出来把最终执行的SQL复制到DM管理工具里跑一遍对比两边结果差异问题就一目了然。5.3 DM管理工具没有对象导航栏的解决办法这个问题可能有人碰到过达梦自带的DM管理工具打开后左侧对象导航栏不显示。一开始我还以为是安装有问题后来发现是工具默认布局的问题。解决办法在菜单栏选择“视图”把“对象导航”勾选上或者直接重置布局。如果还不行检查是不是用了过于老的客户端版本连接新版本的数据库服务建议客户端和服务端版本保持一致。真心建议表结构查看、SQL调试这种活儿优先用达梦自带工具第三方工具即便能连上很多对象信息和调试细节看不全。6. 几个容易忽略的功能模块适配6.1 定时任务模块的额外处理RuoVi的定时任务依赖Quartz相关表有qrtz_job_details、qrtz_triggers等十来张。Quartz官方提供了达梦/Oracle的建表脚本达梦安装目录下的文档里通常有tables_dm.sql不要直接用RuoYi项目里MySQL版本的Quartz表结构脚本。如果已经用MySQL脚本建了删除重建。Quartz表结构对数据类型和索引要求比较严格字段类型不匹配会导致调度器初始化异常。这个模块容易被人忽略但生产环境里定时任务往往很关键建议迁移完第一时间把任务调度跑一遍。6.2 代码生成器模块的注意事项RuoYi的代码生成器需要读取数据库表结构信息。达梦下的系统视图和MySQL的information_schema不同RuoYi代码生成器内置的查询语句是按MySQL写的比如查询表结构时会用到information_schema.tables、information_schema.columns这类视图。在达梦下这些语句可能会报“视图不存在”或“无效的表名”。我的处理方式是把代码生成器相关的Mapper SQL改成查达梦的系统视图达梦对应的视图是USER_TABLES和USER_TAB_COLUMNS基本能拿到表名、列名、数据类型、注释等信息。注意Oracle风格视图的字段名是全大写的映射到RuoYi的TableInfo和ColumnInfo实体时要注意大小写转换否则代码生成器读不到列生成出来的代码会烂。6.3 事务和锁的差异达梦默认的事务隔离级别和锁行为与MySQL InnoDB有差异特别是在高并发下可能出现“事务超时”或“锁等待超时”的情况。如果系统并发不算高感知不强但如果有定时任务和用户操作并发修改同一张表建议把Druid连接池的maxWait和达梦的锁等待超时参数调大一点。具体参数可以在达梦的dm.ini里配置但一般不建议运维时频繁调应用层面可以先保证事务尽早提交长事务会放大锁竞争问题。RuoYi默认事务管理走Spring的Transactional逻辑上没有大问题重点是不要在事务里做耗时操作比如调用外部接口、大量循环更新等。6.4 数据库账号权限控制最后提一句账号权限。建议不要直接用SYSDBA跑业务我建了一个独立的RUOYI账号只授予业务所需的SELECT、INSERT、UPDATE、DELETE以及序列的使用权。代码生成器如果需要读表结构还需要给账号授予查询系统视图的权限。权限不足时最典型的报错是“权限不足”或“无效的授权”。RuoYi里如果出现某个功能在SYSDBA下正常切到业务账号就报错优先检查权限脚本是否把对应表的权限授权完整了。最后分享一点个人的实操体会从MySQL迁到达梦整个过程下来最深的体会是适配并不难难的是把“能不能跑”和“跑得稳”分开来看。能跑只需要把SQL语法和驱动配好跑得稳则要把主键策略、事务行为、权限、工具链都考虑进去。关于适配顺序我强烈建议先把数据源、驱动、连接串这层跑通再动表结构最后才改SQL。顺序反了容易混淆问题来源一会儿怀疑是驱动问题一会儿怀疑是SQL问题排障效率会很低。还有一点是关于代码管理的建议在Git上单独拉一个分支做“达梦适配”所有和方言相关的改动集中在这个分支里这样后续MySQL版本升级时可以快速对比出哪些改动是达梦独有不会影响主分支的MySQL兼容性。如果现在有人问我RuoYi换达梦数据库工作量到底多大我会说纯改造代码层面一到两周足够。前提是愿意花时间把表结构脚本、Mapper SQL、序列和权限都仔细过一遍。如果只想靠数据库兼容模式硬扛后面每一处报错都会变成盲盒那才是真的遥遥无期。
返回列表