H2 数据库到 MySQL 数据迁移

H2 数据库到 MySQL 数据迁移

概述

将某后端项目从 H2 数据库迁移到 MySQL 8.0。

迁移流程

第一步:配置修改

1.1 添加 MySQL 依赖 (pom.xml)

dev profile 中同时保留 H2 和 MySQL 依赖:

xml 复制代码
<profile>
  <id>dev</id>
  <dependencies>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</profile>
1.2 修改配置文件 (application-dev.yml)
yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/c3s_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

第二步:创建 MySQL 数据库

sql 复制代码
CREATE DATABASE YOUR_DATABASE_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

第三步:启动应用创建表结构

在 IntelliJ IDEA 中启动应用,JPA 会自动在 MySQL 中创建所有表。

第四步:从 H2 导出数据

4.1 临时切换回 H2 配置

application-dev.yml 临时改回 H2 配置:

yaml 复制代码
spring:
  datasource:
    url: jdbc:h2:file:./data/YOUR_DATABASE_NAME;DB_CLOSE_DELAY=-1
  h2:
    console:
      enabled: true
      path: /h2-console
4.2 启动应用并访问 H2 Console
4.3 导出数据

在 H2 Console 中执行:

sql 复制代码
SCRIPT TO 'C:\code\backend\backend\data\h2_export.sql';

第五步:转换 H2 导出文件

H2 导出的 SQL 使用 U&'\xxxx' 语法表示 Unicode 字符,需要转换为 UTF-8。

5.1 转换 Unicode (docs/convert-final.py)
python 复制代码
#!/usr/bin/env python3
with open('data/h2_export.sql', 'rb') as f:
    content = f.read()

result = bytearray()
i = 0
while i < len(content):
    if i + 2 < len(content) and content[i] == 0x55 and content[i+1] == 0x26 and content[i+2] == 0x27:
        j = i + 3
        result.append(0x27)
        while j < len(content):
            if content[j] == 0x27:
                break
            elif content[j] == 0x5C and j + 4 < len(content):
                hex_bytes = content[j+1:j+5]
                try:
                    codepoint = int(hex_bytes, 16)
                    result.extend(chr(codepoint).encode('utf-8'))
                    j += 5
                    continue
                except (ValueError, OverflowError):
                    result.append(content[j])
                    j += 1
            else:
                result.append(content[j])
                j += 1
        result.append(0x27)
        i = j + 1
    else:
        result.append(content[i])
        i += 1

with open('data/h2_export_final.sql', 'wb') as f:
    f.write(bytes(result))

运行:python docs/convert-final.py

5.2 提取 INSERT 语句 (docs/extract-inserts.py)
python 复制代码
#!/usr/bin/env python3
import re

with open('data/h2_export_final.sql', 'r', encoding='utf-8') as f:
    content = f.read()

# 从 CREATE TABLE 提取列名
table_columns = {}
create_pattern = r'CREATE CACHED TABLE "PUBLIC"\."(\w+)"\(\s*(.*?)\);'
for match in re.finditer(create_pattern, content, re.DOTALL):
    table_name = match.group(1)
    cols_text = match.group(2)
    columns = []
    for line in cols_text.strip().split('\n'):
        line = line.strip()
        if line and not line.startswith('ALTER') and not line.startswith('--'):
            col_name = line.split()[0].strip('"')
            columns.append(col_name)
    table_columns[table_name] = columns

# 提取 INSERT 语句
insert_pattern = r'INSERT INTO "PUBLIC"\."(\w+)" VALUES\s*\n?(.*?);'
matches = re.findall(insert_pattern, content, re.DOTALL)

output_lines = ['SET NAMES utf8mb4;', 'SET CHARACTER SET utf8mb4;', 'SET FOREIGN_KEY_CHECKS=0;', '']

for table_name, values in matches:
    clean_values = values.strip()
    clean_values = re.sub(r"TIMESTAMP '([^']+)'", r"'\1'", clean_values)
    clean_values = re.sub(r"DATE '([^']+)'", r"'\1'", clean_values)
    clean_values = clean_values.replace('"PUBLIC".', '')
    clean_values = clean_values.replace('TRUE', '1').replace('FALSE', '0')

    if table_name in table_columns:
        cols = ', '.join([f'`{c}`' for c in table_columns[table_name]])
        output_lines.append(f'INSERT INTO `{table_name}` ({cols}) VALUES')
    else:
        output_lines.append(f'INSERT INTO `{table_name}` VALUES')
    output_lines.append(clean_values + ';')
    output_lines.append('')

output_lines.append('SET FOREIGN_KEY_CHECKS=1;')

with open('data/h2_mysql_import.sql', 'w', encoding='utf-8') as f:
    f.write('\n'.join(output_lines))

运行:python docs/extract-inserts.py

第六步:处理 MySQL 兼容性问题

6.1 添加缺失列

H2 和 MySQL 表结构可能有差异,需要手动添加缺失列:

sql 复制代码
-- 某表的 system 是 MySQL 保留字,改为 sys_source
-- 按需创建
6.2 修改保留字

XXX.ktsystem 改为 sys_source

kotlin 复制代码
@Column(name = "sys_source")
var system: String? = null

第七步:逐表导入数据

将导入文件按表拆分后逐个导入:

bash 复制代码
# 拆分(docs/extract-inserts.py 已自动拆分到 data/sql/ 目录)
# 逐表导入
for table in YOUR_TABLE_NAME1、YOUR_TABLE_NAME2 ...; do
    mysql -u root -proot c3s_dev < "data/sql/${table}.sql"
done

第八步:恢复 MySQL 配置

application-dev.yml 恢复为 MySQL 配置,重启应用。


脚本清单

脚本 用途
docs/convert-final.py 将 H2 的 U& 语法转换为 UTF-8
docs/extract-inserts.py 从转换后的 SQL 提取 INSERT 语句并添加列名

产物清单

文件 说明
data/h2_export.sql H2 原始导出
data/h2_export_final.sql Unicode 转换后的 SQL
data/h2_mysql_import.sql 最终 MySQL 导入文件
data/sql/*.sql 按表拆分的导入文件

注意事项

  1. 列顺序:H2 和 MySQL 的列顺序可能不同,INSERT 必须指定列名
  2. 保留字system 是 MySQL 保留字,需要改名或加反引号
  3. NOT NULL:H2 允许某些列为 NULL,MySQL 不允许,需要调整表结构
  4. Unicode :H2 使用 U&'\xxxx' 语法,需要转换为 UTF-8
  5. BOOLEAN :H2 的 TRUE/FALSE 需转换为 MySQL 的 1/0
  6. TIMESTAMP/DATE :H2 的 TIMESTAMP 'xxx' 需转换为 'xxx'
相关推荐
AOwhisky1 小时前
MySQL 学习笔记(第一期):数据库基础与 MySQL 初探
运维·数据库·笔记·学习·mysql·云计算
数据库小学妹1 小时前
MySQL ORDER BY 深度解析:Using temporary 与 Using filesort 的底层机制及索引优化实战
数据库·经验分享·mysql·性能优化·dba
可乐ea2 小时前
【知识获取与分享社区项目 | 项目日记第 21 天】索引构建与联想建议:Outbox 增量更新 + Completion Suggester
java·大数据·mysql·elasticsearch·搜索引擎
RainCity2 小时前
Java Swing 自定义组件库分享(十一)
java·笔记·后端
好家伙VCC2 小时前
Qdrant + LangChain 实战:构建毫秒级语义检索服务
java·langchain
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第93题】【Mysql篇】第23题:从查找速度来看,聚集索引和非聚集索引哪个更快?
java·开发语言·数据库·mysql·面试
摇滚侠2 小时前
JDBC 基础到高级一套通关!高级篇 28-40
java
WPF工业上位机2 小时前
YXGK.FakeVM数据库示例
jvm·数据库·oracle
牛奔2 小时前
如何让 GORM 打印 SQL 语句?三种方式全解析
数据库·sql