
你手头有个攒了几年的老系统数据库一直跑在某个商业数据库上突然说要响应国产化替换第一反应是什么我第一反应是头疼。但真上手之后发现达梦DM这个国产数据库语法和Oracle高度兼容迁移起来没有想象中那么恐怖。这篇文章就是我把Spring Boot MyBatis-Plus服务切换到达梦数据库时沉淀下来的指令速查和实战笔记内容包括日常运维要用的达梦SQL、JDBC驱动配置、分页插件适配、序列主键方案以及一堆只有踩过才记得住的坑。这篇笔记适合那些正在做数据库国产化改造的团队尤其是原来用Oracle、MySQL现在要切到达梦而且项目是基于Spring Boot体系、持久层用MyBatis-Plus的同行。我会把可复用的配置和代码直接贴出来也会把排查过的报错整理成速查表尽量让后来人少走弯路。1. 达梦选型思路与整体方案拆解先说为什么这个项目最终选了达梦。客户环境要求数据库必须国产化主流的选项其实就是达梦、人大金仓、GBase这几个。因为老系统底子是Oracle语法写的存储过程、触发器、序列挑来挑去发现达梦的Oracle兼容模式做得最省心某些PL/SQL块甚至能直接原样跑通这就在迁移成本上占了很大优势。技术栈这边没有搞花活就是市面上最常见的组合Spring Boot 2.7.x MyBatis-Plus 3.5.x 达梦DM8。之所以坚持用MyBatis-Plus而不是完全手写SQL是因为老项目里大量的单表CRUD、分页查询、逻辑删除都是依赖MyBatis-Plus的BaseMapper如果改成纯MyBatis或者JPA改造成本和控制风险都会成倍上升。我们要的是“最小改动切换数据库”不是借机重构业务。整体方案拆开看核心工作其实就四块。第一是基础设施也就是达梦数据库的实例、表空间、用户、权限这些要建好并且把老系统的库表结构、数据搬过去。第二是应用接入Spring Boot工程里要换成达梦驱动、改数据源配置、调整连接池参数。第三是持久层兼容MyBatis-Plus的各种内置方法、分页插件、主键策略要能在达梦上正常工作。第四是回归与清理把SQL方言差异、关键字冲突、大小写问题、类型映射问题挨个扫一遍。里面对业务代码侵入最大的其实是主键策略。老系统在Oracle上用的是序列不是自增列MySQL分页那种写法在达梦上也有细微差别好在达梦对Oracle的序列语法是原生支持的所以复用序列方案就不需要大改代码。这一点在后面会有专门一节来说。2. 达梦数据库常用指令速查既然标题里明确写了“达梦数据库指令”这一步我就把实际用到的指令按场景整理出来。这些都是在DM8版本上验证过的但达梦不同小版本的细节会有差异比如某些系统视图名称、工具命令参数建议动手之前先看一眼自己环境里的版本文档。2.1 服务启动与Disql登录达梦安装完之后日常打交道最多的就是disql这个命令行客户端地位类似于Oracle的sqlplus。登录命令很直观disql SYSDBA/SYSDBAlocalhost:5236如果连接远程库把localhost换成目标IP即可。5236是达梦默认端口。登录后第一件事我习惯确认版本和实例状态-- 查看版本信息 SELECT * FROM v$version; -- 查看实例状态正常应为 OPEN SELECT STATUS FROM v$instance;如果忘了密码那就需要DBA账号去重置。实际项目里我不会让开发人员直接用SYSDBA而是单独建业务账号下一小节会讲怎么建。2.2 表空间、用户与权限达梦的逻辑结构和Oracle很像用户USER和模式SCHEMA是一一对应的创建一个用户的同时就会创建同名模式。表空间则是独立的一层先有表空间再让用户默认使用它。-- 创建数据表空间 CREATE TABLESPACE TS_APP DATAFILE /dm8/data/DAMENG/TS_APP.DBF SIZE 128M AUTOEXTEND ON NEXT 64M MAXSIZE 2048M; -- 创建用户指定默认表空间 CREATE USER APP_USER IDENTIFIED BY App123 DEFAULT TABLESPACE TS_APP; -- 授予基本权限RESOURCE角色包含建表、建序列等开发所需权限 GRANT RESOURCE TO APP_USER; -- 生产环境不建议直接给DBA但测试环境我经常会图省事给一下 GRANT DBA TO APP_USER;注意IDENTIFIED BY后面用的双引号包密码这是因为密码里往往有特殊字符。在达梦命令行里字符串常量用单引号标识符和带特殊字符的密码才用双引号这个习惯一定要建立起来不然密码带或者#号时会非常折腾。建好用户之后直接通过JDBC或disql用APP_USER登录默认就在APP_USER这个模式下操作。这样最大好处是SQL里不需要到处写APP_USER.TB_USER这种带模式前缀的写法只要把当前模式切对就行。2.3 表、序列、索引等常用DDL新建表的语法和Oracle非常接近这里给一个带自增列和默认值的例子CREATE TABLE TB_USER ( ID INT IDENTITY(1,1) PRIMARY KEY, USER_NAME VARCHAR(64) NOT NULL, AGE INT, CREATE_TIME DATETIME DEFAULT SYSDATE );要是老系统原来就是Oracle序列方案那就把ID改成普通数字列然后用序列生成CREATE SEQUENCE SEQ_USER_ID START WITH 1 INCREMENT BY 1; CREATE TABLE TB_USER ( ID INT PRIMARY KEY, USER_NAME VARCHAR(64) NOT NULL, CREATE_TIME DATETIME DEFAULT SYSDATE );我这次项目里其实两套都遇到过自增列切到MyBatis-Plus可以配IdType.AUTO序列方案要配IdType.INPUT后面讲代码时再细说。索引和注释的写法也和Oracle一致CREATE INDEX IDX_USER_NAME ON TB_USER(USER_NAME); COMMENT ON COLUMN TB_USER.USER_NAME IS 用户名; COMMENT ON TABLE TB_USER IS 用户表;日常开发中查表结构、查序列用得比较多记这几个查询语句就够了-- 查看当前用户下所有表 SELECT TABLE_NAME FROM USER_TABLES; -- 查看表字段信息 SELECT * FROM USER_TAB_COLUMNS WHERE TABLE_NAME TB_USER; -- 查看序列 SELECT * FROM USER_SEQUENCES WHERE SEQUENCE_NAME SEQ_USER_ID;2.4 数据导入导出与备份数据迁移时不可能手动一条条插达梦自带了一套命令行工具典型的有dexp/dimp、dmfldr、dmrman。dexp和dimp的角色对应Oracle的exp/imp按用户、按表导出都支持。我最常用的是按用户模式整体导出dexp USERIDSYSDBA/SYSDBAlocalhost:5236 FILE/backup/app_user.dmp LOG/backup/exp.log OWNERAPP_USER导入到目标库时先建好同名的用户和表空间然后执行dimp USERIDSYSDBA/SYSDBAlocalhost:5236 FILE/backup/app_user.dmp LOG/backup/imp.log SCHEMAAPP_USER另一个很实用的工具是dmfldr相当于Oracle的sqlldr适合从文本文件批量灌数据。老系统导出CSV后用dmfldr往达梦里灌速度要比逐条insert快一个量级。命令大致是dmfldr USERIDAPP_USER/App123localhost:5236 CONTROL/data/load.ctl DATA/data/user_data.csv控制文件里写列分隔符、表名、字段顺序这些。这个工具网上资料相对少但实战价值很高大批量历史数据同步时能省很多时间。2.5 性能排查与状态查看SQL切换数据库之后总会遇到慢查询或者锁等待的问题。达梦的系统视图基本复刻了Oracle熟悉Oracle的人上手会很顺。-- 查看当前会话 SELECT * FROM V$SESSIONS WHERE STATE ACTIVE; -- 查看锁等待 SELECT * FROM V$LOCK WHERE BLOCKED 1; -- 查看执行时间较长的SQL SELECT * FROM V$SQL_HISTORY WHERE ELAPSED_TIME 1000;这些视图名在不同版本可能略有差异如果环境里报视图不存在可以用SELECT NAME FROM V$DYNAMIC_TABLES WHERE NAME LIKE %LOCK%先搜一下可用视图再调整SQL。3. Spring Boot集成达梦依赖、数据源与基础配置数据库侧准备好之后应用侧的改动其实不大但细节很多尤其是驱动依赖和连接池兼容性这两块第一批踩坑的人基本都载在这里。3.1 Maven驱动依赖与本地Jar安装达梦官方JDBC驱动在Maven中央仓库不一定能直接拉到稳定版本我在项目里用的是DmJdbcDriver18。如果你在私服里正好配好了中央仓库代理直接加坐标dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.2.192/version /dependency如果仓库里找不到最常见的做法是把安装目录dmdbms/drivers/jdbc下的DmJdbcDriver18.jar复制出来用install-file命令装进本地Maven仓库mvn install:install-file -DfileDmJdbcDriver18.jar -DgroupIdcom.dameng -DartifactIdDmJdbcDriver18 -Dversion8.1.2.192 -Dpackagingjar这里提醒一句驱动版本尽量和数据库服务端版本保持一致至少大版本要对齐。我见过有人拿着老驱动连新库结果连接正常但某些类型返回异常排查半天才发现是驱动太老。3.2 数据源URL与连接池参数application.yml里的数据源配置如下这是我实际跑通的版本spring: datasource: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://127.0.0.1:5236?schemaAPP_USER username: APP_USER password: App123URL参数里的schemaAPP_USER很关键。因为业务账号和模式同名正常情况下登录后默认就在自己的模式里但项目里不同环境切换时容易混乱显式指定schema可以避免查错模式。项目里如果用的是Druid连接池初始化SQL和检测SQL要留意一下。达梦兼容Oracle的DUAL表所以SELECT 1 FROM DUAL这类探活SQL没问题。不过Druid对达梦驱动类型识别有时不够完善如果启动时出现以下异常java.sql.SQLException: 不支持的数据库类型需要在Druid配置里显式指定连接池适配或者换用HikariCP。我这个项目直接用的HikariCP它是Spring Boot默认连接池与达梦驱动配合很稳定省去了很多适配问题。HikariCP的完整配置如下spring: datasource: hikari: pool-name: DmHikariPool minimum-idle: 5 maximum-pool-size: 15 connection-timeout: 30000 connection-test-query: SELECT 1 FROM DUAL3.3 MyBatis-Plus核心配置MyBatis-Plus的全局配置基本沿用旧项目的只需注意两个点开启驼峰映射以及根据主键方案调整id-type。mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: input logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0id-type设成input是因为老库用了序列具体原因后面马上说。map-underscore-to-camel-case必须开启否则USER_NAME这种列名映射不到实体的userName字段上。3.4 分页插件必须显式指定数据库类型MyBatis-Plus的分页功能不是自动适配所有数据库的必须通过PaginationInnerInterceptor指定DbType。这是我踩过的一个很典型的坑分页插件不指定DbType时默认从连接元数据里判断而达梦不在它能自动识别的首选名单里结果一执行分页查询就报“请指定数据库类型”或“unsupported database type”。正确的配置写法如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.DM); pagination.setMaxLimit(500L); pagination.setOverflow(false); interceptor.addInnerInterceptor(pagination); return interceptor; } }其中setOverflow(false)表示超过最大分页限制时不自动回退直接报错这是为了防手滑查出全表。至此应用框架层面的接入已经完成接下来才是真正写业务代码时要面对的各种兼容性问题。4. MyBatis-Plus适配实战主键、SQL写法与代码生成配置配好只能说“项目能启动”业务能跑通还差得远。这一节讲的是写代码过程中最核心的几个适配点。4.1 实体类与表名的映射习惯达梦默认会把不带双引号的表名、列名转成大写存储而MyBatis-Plus默认不会自动改大小写。所以实体类上要明明白白写清楚TableName(TB_USER) public class User { TableId(value ID, type IdType.INPUT) private Long id; TableField(USER_NAME) private String userName; private Integer age; }只要库表定义和实体注解都用统一的大小写MyBatis-Plus生成的SQL就不会出问题。最怕的是建表时一会儿用user_abc一会儿用USER_ABC这会让达梦把两种标识符当作两个不同对象应用侧怎么对齐都别扭。我的建议是库表设计统一不带双引号、统一大写让达梦按默认行为去处理。4.2 主键策略自增列还是序列这一节是标题里“MyBatis-Plus使用”含金量最高的部分。如果新表用的IDENTITY自增列那就简单实体类配置TableId(value ID, type IdType.AUTO) private Long id;MyBatis-Plus插入时会自动把自增列回填到实体的id属性上。但老系统的表很多不是自增列而是靠SEQ_USER_ID.NEXTVAL取主键。这种情况下如果还用IdType.AUTO就会得到主键为NULL的插入SQL数据库直接报“不能为空”。解决办法是改写XML的insert语句显式取序列值。最简单的XML方案insert idinsert parameterTypeUser INSERT INTO TB_USER(ID, USER_NAME, AGE) VALUES(SEQ_USER_ID.NEXTVAL, #{userName}, #{age}) /insert如果不想每条insert都手写XML还可以用MyBatis-Plus提供的KeySequence注解配合IdType.INPUT让MyBatis-Plus在插入前自动调用序列生成主键。这个能力当初是为Oracle设计的因为达梦兼容Oracle的序列语法所以也能直接用KeySequence(SEQ_USER_ID) TableName(TB_USER) public class User { TableId(value ID, type IdType.INPUT) private Long id; // 其他字段 }这样配置之后调用userMapper.insert(user)时MyBatis-Plus会自动执行SELECT SEQ_USER_ID.NEXTVAL FROM DUAL之类的方式拿到主键并回填业务代码不需要关心主键生成逻辑。这里有个细节很容易被忽略如果项目同时存在多张表、多个序列那么每张表都要在KeySequence里指定对应的序列名。偷懒只在一个表上配其他表插入时照样报主键为空。4.3 XML手写SQL与关键字转义MyBatis-Plus的BaseMapper能覆盖大部分单表操作但复杂SQL还是得写XML。写XML时要特别小心达梦的保留字。比如USER、LEVEL、ROWNUM、COMMENT这些词在达梦里都有特殊含义如果恰好是你表里的字段名请务必用双引号包起来select idselectByName resultTypeUser SELECT ID, USER_NAME, AGE FROM TB_USER WHERE AGE gt; #{minAge} ORDER BY ID DESC /selectXML里小于号要写成lt;这是MyBatis原有的规矩和达梦无关但换库之后很多人调试时不注意报语法错第一反应是数据库问题结果被自己写的XML坑了半天。还有一个和Oracle一致的坑SQL语句结尾不要带分号。MyBatis执行时会对语句做处理如果XML里的SQL末尾带了分号达梦有时候会报“无效的字符”。这个习惯在MySQL下没问题换到达梦就要改掉。4.4 代码生成器的两种落地方式用MyBatis-Plus自带的AutoGenerator做代码生成时默认的数据库类型枚举里没有达梦的专属实现。网上不少方案是改源码或自己继承AbstractDatabaseQuery写一个DmQuery代码量不小对很多人来说性价比不高。我实际操作中用了两个更务实的方案。方案A如果目标表结构在某个Oracle测试库里还有一份直接让代码生成器按Oracle来生成生成的实体、Mapper、XML在达梦上基本能直接使用因为达梦兼容Oracle的元数据查询方式。前提是表名、列名统一大写。方案B如果只有达梦环境那就自己写一个极简的查询实现。思路是继承MyBatis-Plus的AbstractDatabaseQuery用JDBC的DatabaseMetaData接口取表信息和字段信息去构造TableInfo和TableField。核心方法就两个——queryTables和queryTableFields。我在这里只写一个骨架你们按自己项目的包结构填充即可public class DmDatabaseQuery extends AbstractDatabaseQuery { public DmDatabaseQuery(DataSource dataSource) { super(dataSource); } Override public ListTableInfo queryTables(String schema) { // 使用 connection.getMetaData().getTables(null, schema, %, new String[]{TABLE}) // 循环构造 TableInfo return null; } Override public ListTableField queryTableFields(String tableName) { // 使用 connection.getMetaData().getColumns(null, null, tableName, %) // 根据 COLUMN_NAME、TYPE_NAME、REMARKS 构造 TableField return null; } }有了这个类再把AutoGenerator的dataSource配置换成达梦数据源就可以让代码生成器直接跑在达梦上。如果不想在这个环节花太多时间就先按方案A处理等业务跑通后再回头优化生成器也不迟。4.5 逻辑删除、自动填充等内置功能的兼容性MyBatis-Plus的逻辑删除、字段自动填充、乐观锁这些能力实现上不依赖数据库特有语法换到达梦后基本不需要改。我项目中就用到了逻辑删除和自动填充TableLogic TableField(DELETED) private Integer deleted; TableField(value CREATE_TIME, fill FieldFill.INSERT) private LocalDateTime createTime;自动填充需要实现MetaObjectHandler逻辑上跟数据库无关直接复用。唯一要留意的是时间字段类型达梦里DATETIME对应Java的LocalDateTime是没问题的但如果是TIMESTAMP类型且精度较高建议在实体里用LocalDateTime接收不要用java.util.Date避免格式化差异。5. 问题排查与避坑清单这一节汇总我在切换过程中真实遇到过的报错和解决办法按场景排好方便直接对照排查。5.1 标识符大小写不一致导致“列名无效”症状MyBatis-Plus执行插入或查询时报无效的列名但数据库里明明有这个字段。原因几乎都是列名大小写不一致。达梦在未加双引号时统一存大写而Java代码里如果字段名是小写生成的SQL里列名变成了小写此时达梦会把小写列名当成另一个对象去解析自然就找不到了。解决方式就是前面说的实体类上把TableName、TableField都显式写清楚并且和库表定义的大小写保持一致。5.2 分页查询报“请指定数据库类型”症状分页接口一调用就报错提示无法识别数据库类型。原因就是分页插件没指定DbType.DM导致MyBatis-Plus无法确定分页方言。解决方案见3.4节注意DbType.DM这个枚举是MyBatis-Plus内置的不需要额外引入包。5.3 插入记录时主键为空症状insert执行成功但报“列ID不能为空”或者插入后实体主键没有回填。原因几乎可以断定是主键策略没配对。如果表是序列主键就用4.2节的两套方案如果是自增列就改回IdType.AUTO。还有一种隐蔽情况是application.yml里配置了id-type: input但某张表的实体类用了IdType.AUTO全局配置和注解配置冲突以注解优先。5.4 驱动类找不到或连接超时症状启动直接报ClassNotFoundException: dm.jdbc.driver.DmDriver或者提示Connection refused。前者是驱动jar没进classpath重新install到本地仓库或者检查私服坐标。后者优先确认达梦服务是否启动、端口是否放通然后在服务器上执行disql测一下连通性如果disql能连而应用不能连再去看防火墙和网络策略。5.5 数据类型映射异常症状查询时报无法转换为XXX类型多见于Number、CLOB字段。达梦的NUMBER类型对应Java的BigDecimal实体类里如果用Long或Integer接收在数据量大或精度高时可能出现转换异常。所以定义实体时不要偷懒整数列就用Long/Integer有小数的用BigDecimal字符串大字段用String接收CLOB时要注意超过一定长度后一些驱动版本会返回特殊类型必要时在SQL里显式做TO_CHAR(CLOB_COLUMN)转换。5.6 事务回滚与锁等待症状某个事务方法超时后面其他事务全部卡住。达梦默认事务行为和Oracle相似修改数据后需要commitSpring的Transactional会帮忙处理一般问题不大。但如果排查锁等待可以用2.5节里的V$LOCK视图。我遇到过一次因为代码里有长事务批量更新几十万行没分批直接把别的会话堵住了后来把批量任务拆成小批次提交才解决。5.7 问题速查表症状大概率原因处理办法启动报驱动类找不到驱动jar未引入或版本不对重新安装本地Maven依赖连接被拒绝服务未启动/端口不通先disql直连验证分页报数据库类型错误分页插件没配DbType.DM显式指定数据库类型插入主键为空序列/自增策略配置错误按表配置IdType.INPUT或AUTO列名无效标识符大小写不一致统一对象名大小写XML报无效字符SQL末尾带分号去掉末尾分号类型转换异常Number到Long/Integer不匹配改用BigDecimal或调整实体类型事务卡死长事务或锁等待拆批提交、查V$LOCK定位会话6. 项目落地阶段的检查清单与个人体会前五节基本把技术细节覆盖了最后再聊一点项目落地层面的经验这些东西在官方文档里是找不到的。6.1 迁移前先做SQL兼容性盘点正式切库之前把老系统里手写的SQL全部过一遍。重点排查这几类一是分页写法如果老系统用了MySQL的LIMIT要确认MyBatis-Plus分页插件是否正确转换二是日期函数比如SYSDATE、TO_CHAR、TO_DATE达梦基本兼容Oracle但格式串细节要测三是字符串拼接Oracle风格用||这个达梦也支持。把这些集中测试一下能少挨很多夜里的告警电话。6.2 上线前必做的几项检查第一确认所有表的序列、触发器、自增列状态正常尤其是自增列当前值要大于历史数据最大值。第二连接池的探活SQL是否兼容HikariCP配置了SELECT 1 FROM DUAL可以放心。第三数据源URL里的schema是否匹配实际用户。第四把MyBatis-Plus的SQL日志打开观察生成的SQL有没有奇怪的引号或大小写问题。第五做一次全链路压测至少把最大的几张表连接查询、分页、批量插入都跑一遍。6.3 如实说几句个人体会切换达梦这件事技术难度不算高真正消耗精力的是那些零零碎碎的方言差异和数据本身的迁移校验。如果你的项目和我一样大量使用MyBatis-Plus的BaseMapper那么切换到达梦的过程其实比想象中顺滑因为框架已经把大部分底层差异挡住了。我个人实际使用中最满意的是达梦对Oracle序列和PL/SQL的兼容这让我在改造存储过程时几乎没有重写代码。但我也要提醒一句不要因为兼容性好就放松测试环节尤其是字段大小写、保留字、数据类型映射这些问题往往不在迁移当天暴露而是过了一两周某个偏门接口第一次被调用时才炸出来。把本文的避坑清单转成你们团队自己的检查清单上线前逐项打过勾之后的日子就会好过很多。