最近几年国产数据库发展迅猛,越来越多的企业开始考虑将核心业务系统从国外数据库迁移到国产数据库。达梦数据库(DM8)作为国产数据库的佼佼者,在很多关键行业领域已经得到了广泛应用。我最近刚完成了一个若依项目的数据库迁移工作,从MySQL切换到达梦数据库,整个过程踩了不少坑,也积累了一些经验。
若依作为一个流行的快速开发框架,很多企业都在使用。当我们需要把基于若依的系统从MySQL迁移到达梦时,最大的挑战在于两者在SQL语法、函数支持等方面的差异。MySQL中常用的replace into、find_in_set等函数在达梦中并不支持,需要找到对应的替代方案。此外,达梦的JDBC驱动配置、代码生成器的适配也都是需要特别注意的地方。
迁移过程看似简单,但实际上涉及到驱动引入、配置变更、SQL语法适配、代码生成器改造等多个环节。下面我就结合实战经验,详细讲解每个环节的具体操作和注意事项。
达梦数据库的安装相对简单,基本上按照官方文档的指引一步步操作即可。安装完成后,我们需要特别注意达梦的默认端口是5236,默认管理员账号是SYSDBA,密码也是SYSDBA。这些信息在后续的配置中都会用到。
安装完成后,驱动文件位于安装目录的\dmdbms\drivers\jdbc下。这里有三个不同版本的驱动包,对应不同的JDK版本:
由于SpringBoot没有内置对达梦数据库的支持,我们需要手动引入驱动。具体步骤如下:
xml复制<!-- 达梦数据库驱动 -->
<dependency>
<groupId>com.dm</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/DmJdbcDriver18.jar</systemPath>
</dependency>
这里有几个关键点需要注意:
接下来需要修改application-druid.yml文件中的数据库配置。与MySQL配置相比,主要变化如下:
yaml复制spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: dm.jdbc.driver.DmDriver
druid:
# 主库数据源
master:
url: jdbc:dm://localhost:5236/
username: SYSDBA
password: SYSDBA
主要变更点:
MySQL中的replace into语句在达梦中不被支持,需要用merge into替代。以SysUserOnlineMapper.xml中的saveOnline方法为例:
原MySQL语句:
xml复制<INSERT id="saveOnline" parameterType="SysUserOnline">
REPLACE INTO sys_user_online(...)
VALUES (...)
</INSERT>
达梦适配后的语句:
xml复制<INSERT id="saveOnline" parameterType="SysUserOnline">
MERGE INTO sys_user_online
USING (SELECT ... from dual) d
ON sys_user_online.sessionId = d.sessionId
WHEN matched THEN UPDATE SET ...
WHEN NOT matched THEN INSERT (...) VALUES (...)
</INSERT>
merge into语法虽然复杂一些,但功能更强大,可以实现条件更新和插入。在实际使用中,我发现达梦对merge into的支持非常完善,性能也很好。
find_in_set是MySQL中常用的函数,但在达梦中不存在。我们可以用like配合concat来模拟实现类似功能。以SysDeptMapper.xml为例:
原MySQL语句:
xml复制<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
select * from sys_dept where find_in_set(#{deptId}, ancestors)
</select>
达梦适配后的语句:
xml复制<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
select * from sys_dept where ancestors like concat('%', #{deptId}, '%')
</select>
需要注意的是,like匹配的精度不如find_in_set精确,可能会匹配到不相关的记录。如果对精度要求很高,可能需要考虑其他实现方式,比如将ancestors字段改为JSON格式存储。
除了上述两个典型函数外,还有一些其他需要注意的函数差异:
在实际迁移过程中,建议先全面扫描项目中的所有SQL语句,找出所有不兼容的函数和语法,然后统一制定替换方案。
若依框架自带的代码生成器默认是针对MySQL设计的,迁移到达梦数据库后需要进行相应调整。主要修改集中在GenTableColumnMapper.xml和GenTableMapper.xml两个文件中。
原MySQL查询语句:
xml复制<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
select column_name,
(case when (is_nullable = 'no' && column_key != 'PRI') then '1' else null end) as is_required,
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
ordinal_position as sort,
column_comment,
(case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
column_type
from information_schema.columns
where table_schema = (select database()) and table_name = (#{tableName})
order by ordinal_position
</select>
达梦适配后的查询:
xml复制<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
SELECT t3.COLUMN_NAME AS column_name,
CASE WHEN t3.NULLABLE = 'N' AND t4.CONSTRAINT_TYPE != 'P' THEN '1' ELSE NULL END AS is_required,
IF(t4.CONSTRAINT_TYPE = 'P', 1, 0) AS is_pk,
t3.COLUMN_ID AS sort,
t5.COMMENTS AS column_comment,
CASE WHEN (t3.TYPE = 'INT' OR t3.TYPE = 'INTEGER' OR t3.TYPE = 'BIGINT' OR t3.TYPE = 'TINYINT' OR t3.TYPE = 'SMALLINT') AND t4.CONSTRAINT_TYPE = 'P' THEN '1' ELSE '0' END AS is_increment,
DATA_TYPE AS DATA_TYPE
FROM (
SELECT COLUMN_NAME, COLUMN_ID, CONCAT(DATA_TYPE, '(', DATA_LENGTH, ')') AS DATA_TYPE,
DATA_TYPE AS TYPE, TABLE_NAME, NULLABLE
FROM SYS.USER_TAB_COLUMNS
WHERE table_name = #{tableName}
) t3
LEFT JOIN (
SELECT COMMENTS, COLUMN_NAME, TABLE_NAME
FROM SYS.USER_COL_COMMENTS
) t5 ON t3.COLUMN_NAME = t5.COLUMN_NAME AND t3.TABLE_NAME = t5.TABLE_NAME
LEFT JOIN (
SELECT t1.CONSTRAINT_TYPE, t1.OWNER, t1.TABLE_NAME, t2.CONSTRAINT_NAME, t2.COLUMN_NAME
FROM (
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, OWNER, TABLE_NAME
FROM SYS.USER_CONSTRAINTS
) t1
INNER JOIN (
SELECT CONSTRAINT_NAME, OWNER, TABLE_NAME, COLUMN_NAME
FROM SYS.USER_CONS_COLUMNS
) t2 ON t1.TABLE_NAME = t2.TABLE_NAME AND t1.CONSTRAINT_NAME = t2.CONSTRAINT_NAME
WHERE t1.CONSTRAINT_TYPE = 'P'
) t4 ON t3.COLUMN_NAME = t4.COLUMN_NAME AND t3.TABLE_NAME = t4.TABLE_NAME
ORDER BY t3.COLUMN_ID
</select>
这个修改比较复杂,主要是因为达梦和MySQL在系统表结构上有很大差异。达梦的系统表分布在SYS模式下,需要通过多表关联查询才能获取完整的字段信息。
原MySQL查询:
xml复制<select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time
from information_schema.tables
where table_schema = (select database())
AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT IN (select table_name from gen_table)
<if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if>
order by create_time desc
</select>
达梦适配后的查询:
xml复制<select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult">
SELECT t1.TABLE_NAME AS table_name, t2.COMMENTS AS table_comment,
NULL AS create_time, NULL AS update_time
FROM SYS.USER_TABLES t1
INNER JOIN SYS.USER_TAB_COMMENTS t2 ON t1.TABLE_NAME = t2.TABLE_NAME
WHERE t1.TABLE_NAME NOT LIKE 'qrtz_%' AND t1.TABLE_NAME NOT LIKE 'gen_%'
AND t1.TABLE_NAME NOT IN (SELECT table_name AS TABLE_NAME FROM gen_table)
<if test="tableName != null and tableName != ''">
AND lower(t1.TABLE_NAME) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(t1.TABLE_NAME) like lower(concat('%', #{tableName}, '%'))
</if>
</select>
这里主要的变化是从information_schema.tables改为查询SYS.USER_TABLES和SYS.USER_TAB_COMMENTS。需要注意的是,达梦的系统表中没有直接提供表的创建时间和修改时间,所以这里设置为NULL。
在实际迁移过程中,我遇到了不少问题,这里总结几个典型的案例和解决方案。
问题现象:应用启动时报错,提示找不到dm.jdbc.driver.DmDriver。
解决方案:
问题现象:执行SQL语句时报语法错误。
解决方案:
问题现象:在高并发场景下出现数据不一致问题。
解决方案:
迁移完成后,可能需要对达梦数据库进行一些性能调优:
完成所有代码修改后,必须进行全面的验证测试。我建议按照以下步骤进行:
在测试过程中,特别注意以下几点:
在最近的一个项目中,我们将若依系统从MySQL迁移到达梦数据库,整个过程花了大约两周时间。其中最大的挑战不是技术上的适配,而是对达梦数据库特性的不熟悉。通过这次迁移,我总结了以下几点经验:
达梦数据库作为国产数据库的代表,在功能完整性、性能表现方面都已经相当成熟。虽然在语法细节上与MySQL有些差异,但通过合理的适配和优化,完全可以满足企业级应用的需求。