最近半年,因为信创要求,我把一个SpringCloud微服务项目从MySQL8迁移到了达梦8。整个过程比想象中曲折,但也积累了不少经验。
这篇文章不聊大道理,直接说5个我真实踩过的坑和对应的解决方案。希望能帮你少走弯路。
- 原库:MySQL 8.0.28
- 目标库:达梦 8.1
- 框架:SpringBoot 2.x(升级到3.x) + SpringCloud + JPA(Hibernate)
- 数据量:核心表百万级
坑1:JPA 自动建表失败——大小写敏感 + 字符集冲突
现象
项目启动,ddl-auto: update 报错:项目使用了JPA,并且auto-ddl设置的为update,在连接达梦数据库的时候,第一次启动没有问题,但是后面重启就会报错,发现错误为重复建表,也就是说已经建好的表没有检测到
table or view does not exist
同时看到达梦里创建的表名变成了大写,代码里用的是小写(mysql习惯)。
原因
达梦 8 默认大小写敏感,且默认字符集是 GB18030,而 MySQL 8 默认是 utf8mb4。
Hibernate 生成表名时,在不同数据库下行为不一致:
- MySQL 下保持代码中的大小写
- 达梦下可能转成大写 解决1:其他配置保持不变,直接贴代码
public class DmStandardTableExporter extends StandardTableExporter {
public DmStandardTableExporter(Dialect dialect) {
super(dialect);
}
@Override
public String[] getSqlCreateStrings(Table table, Metadata metadata, SqlStringGenerationContext context) {
// 保存原始表名和引用状态
String originalName = table.getName();
//判断是否带引号
boolean wasQuoted = table.isQuoted();
// 仅当表名未被显式引用时转换为大写
if (!wasQuoted) {
table.setName(originalName.toUpperCase()); // 统一转为大写
table.setQuoted(false); // 确保不添加引号
}
try {
return super.getSqlCreateStrings(table, metadata, context);
} finally {
// 恢复原始状态(避免影响后续操作)
table.setName(originalName);
table.setQuoted(wasQuoted);
}
}
}
代码作用
这是一个自定义的Hibernate表结构导出器,用于在自动建表(ddl-auto: update/create)时,将表名统一转成大写,解决达梦8大小写敏感导致的"表不存在"问题。
核心逻辑
1. 为什么要这样做?
- 达梦8默认大小写敏感
- 如果代码中表名是
t_user(小写),Hibernate生成的建表SQL可能是t_user或T_USER - 达梦里查询时,不加引号默认转成大写去找,导致
t_user找不到
2. 代码执行流程
// 原始表名:假设是 "t_user"
String originalName = table.getName(); // "t_user"
boolean wasQuoted = table.isQuoted(); // false(没加引号)
// 转成大写
if (!wasQuoted) {
table.setName(originalName.toUpperCase()); // "T_USER"
table.setQuoted(false); // 确保不加引号
}
// 调用父类生成建表SQL
// 最终生成的SQL是:CREATE TABLE T_USER (...)
String[] sqls = super.getSqlCreateStrings(table, metadata, context);
// 恢复原始表名(重要!避免污染其他地方)
table.setName(originalName); // 恢复成 "t_user"
table.setQuoted(wasQuoted); // 恢复成 false
3. 为什么要保存和恢复?
因为Hibernate内部可能多次使用同一个Table对象:
- 第一次生成CREATE TABLE语句
- 后续可能生成索引、外键约束等
如果不恢复,后续操作会一直用大写表名,可能导致不一致。
4. wasQuoted 的判断很关键
// 如果用户显式写了 @Table(name = ""t_user"")
if (wasQuoted) {
// 说明用户强制要求小写+带引号,不要转换
}
这允许开发者通过手动加引号来精确控制表名格式。
完整使用方式
1. 注册这个自定义导出器
@Component
public class MyDmDialect extends DmDialect {
public MyDmDialect() {
super();
}
@Override
public Class<? extends TableExporter> getTableExporter() {
return DmStandardTableExporter.class; // 使用你的自定义类
}
}
2. 配置使用自定义方言
spring:
jpa:
properties:
hibernate:
dialect: com.hwa.support.config.MyDmDialect # 你的自定义方言
坑2:MySQL 8 的窗口函数迁移失败
现象
MySQL 8 里有这样的 SQL:
SELECT
id,
name,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn
FROM product;
到达梦 8 里直接报语法错误。
原因
达梦 8 支持窗口函数,但语法细节不同:
- MySQL 8 的
ROW_NUMBER()等函数基本兼容 SQL 标准 - 达梦需要
AS关键字来定义别名的方式更严格 - 部分窗口函数(如
FIRST_VALUE)在两个库中的默认行为不一致
解决方案
方案一(推荐):使用 JPA 的 @Subselect 或 @Formula 绕过
@Entity
@Subselect("SELECT id, name, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn FROM product")
public class ProductRank {
// ...
}
方案二:手动改写达梦兼容写法
-- 达梦 8 正确写法
SELECT
id,
name,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn
FROM product;
(其实语法一样,但达梦要求 OVER 子句中不能有额外的空格或换行?我遇到过换行就报错的情况,把 SQL 压缩成一行解决)
方案三:用传统聚合 + 自连接替代(性能差,不推荐)
我的建议:迁移前先用工具(如达梦自带的迁移工具 DTS)测试一遍,它会提前报出不兼容的 SQL。
坑3:JSON 函数完全不兼容
现象
MySQL 8 大量使用了 JSON 字段和函数:
SELECT JSON_EXTRACT(extra_info, '$.age') FROM user;
UPDATE user SET extra_info = JSON_SET(extra_info, '$.score', 100);
到达梦 8 里全部报错:JSON_EXTRACT is not a valid function
原因
达梦 8 不支持 MySQL 的 JSON 函数。
达梦有 JSON 数据类型,但操作函数完全不同(JSON_GET、JSON_SET 等),且语法不兼容。
解决方案
方案一(推荐):迁移前把 JSON 字段拆成普通列
-- 原来 extra_info JSON 存了 age, score, address
-- 改成:
ALTER TABLE user ADD COLUMN age INT;
ALTER TABLE user ADD COLUMN score INT;
ALTER TABLE user ADD COLUMN address VARCHAR(255);
这是最彻底、性能最好的方式。
方案二:在应用层做转换
- 查询时:把 JSON 字符串查出来,在 Java 里用 Jackson/Gson 解析
- 更新时:Java 里组装 JSON,再写回数据库
- 代价:失去数据库内 JSON 函数的能力,性能下降
方案三:写一个 SQL 转换工具
我写了一个简单的 JsonFunctionConverter,自动把 JSON_EXTRACT(col, '$.key') 转成达梦的 JSON_GET(col, 'key')。
(有需要可以找我拿)
结论:如果你项目里 JSON 用得多,迁移成本会很高。建议提前评估,能拆列就拆列。
坑4:自增主键 + JPA 的 IDENTITY 策略失效
现象
MySQL 8 里用 AUTO_INCREMENT,JPA 实体:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
迁移到达梦后,插入时报:
null is not allowed for column ID
原因
达梦 8 的自增列有两种实现:
- IDENTITY 列(类似 MySQL AUTO_INCREMENT)
- SEQUENCE 序列(更灵活)
但达梦的 IDENTITY 和 JPA 的 IDENTITY 策略配合时,必须在建表语句中明确指定自增列。
如果表是从 MySQL 迁移过来的(表结构已存在),IDENTITY 属性可能丢失。
解决方案
方案一:重建表时指定 IDENTITY
CREATE TABLE t_order (
id INT IDENTITY(1,1) PRIMARY KEY,
order_no VARCHAR(50)
);
方案二(推荐微服务场景):改用 SEQUENCE 策略
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_seq")
@SequenceGenerator(name = "order_seq", sequenceName = "seq_order_id", allocationSize = 1)
private Long id;
}
达梦里创建序列:
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
方案三:迁移后用 ALTER 重新添加 IDENTITY(达梦支持,但操作复杂)
ALTER TABLE t_order MODIFY id IDENTITY(1,1);
坑点:如果你用了 JPA 的
allocationSize默认值 50,会导致 ID 跳号严重(1, 51, 101...),记得改成 1。
坑5:Hibernate与达梦数据库驱动版本不兼容的典型问题。主要是DmDialect-for-hibernate6-8.jar未能完全实现Hibernate 6所需的IdentityColumnSupport接口。
以下是几种解决方案:
方案1:使用官方推荐的达梦方言(推荐)
达梦官方提供了完整的Hibernate支持:
-
下载官方驱动包:
- 从达梦官网下载最新JDBC驱动(dm8-xxx-jdbc18.jar)
- 通常驱动包中已包含方言实现
-
配置正确的方言:
spring: jpa: database-platform: dm8 hibernate: ddl-auto: update datasource: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://localhost:5236/your_database
方案2:降级或升级驱动版本
-
检查版本兼容性:
Spring Boot 3.x → Hibernate 6.x → 需要达梦Hibernate 6方言 -
尝试不同的驱动组合:
- 使用达梦官方完整包(包含方言)
- 或尝试Hibernate 5.x + Spring Boot 2.7.x(我未试过)
其他坑(多)
- group by 必须全量
POINT()函数。达梦使用POINT(x, y)创建点对象,而MySQL的point()函数是POINT(x, y)。此外,达梦的空间距离计算函数也不同。- navicat编辑的sql特殊字段会添加如
name,达梦报错,需要删除
最后
国产化迁移不是“能不能做”的问题,而是“怎么做更快、更稳”的问题。
如果你也在做 MySQL 8 → 达梦 8 的迁移,希望这篇文章能帮你省下几个通宵。
我在成都,承接:
- MySQL/Oracle → 达梦 8 / 人大金仓 迁移
- SpringCloud 微服务性能调优、线上故障排查
- 企业管理系统全栈开发(Java+Vue2)
按项目 / 按天合作都可以,欢迎私聊。
微:[13618067633]