📖 前言
在国产化浪潮的推动下,越来越多的企业开始将数据库从国外产品迁移到国产数据库。本文将以一个真实的Spring Boot项目为例,详细介绍从PostgreSQL迁移到人大金仓(KingBase)数据库的完整过程,包括遇到的问题、解决方案和最佳实践。
🎯 项目背景
项目名称 :某某调查API系统
技术栈 :Spring Boot + MyBatis Plus + Maven
原数据库 :PostgreSQL 12+
目标数据库 :KingBase V8
迁移原因:国产化要求,数据安全考虑
🏗️ 项目架构概览
survey-system/
├── src/main/java/
│ ├── common/ # 公共组件
│ ├── framework/ # 框架配置
│ └── project/ # 业务模块
├── src/main/resources/
│ ├── mapper/ # MyBatis映射文件
│ └── application.yml # 配置文件
└── pom.xml # 依赖管理
🔄 迁移步骤详解
第一步:依赖配置调整
1.1 移除PostgreSQL依赖
xml
<!-- pom.xml -->
<!-- 注释掉PostgreSQL驱动 -->
<!--
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.23</version>
</dependency>
-->
1.2 添加KingBase驱动
xml
<!-- 添加KingBase驱动 -->
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>8.6.0</version>
</dependency>
注意事项:
- 确保使用正确的驱动版本
- 避免重复依赖,只保留一个版本
- 建议使用官方推荐的驱动版本
第二步:数据库连接配置
2.1 基础连接配置
yaml
# application.yml
spring:
datasource:
driver-class-name: com.kingbase8.Driver
url: jdbc:kingbase8://localhost:54321/surveysystem
username: your_username
password: your_password
2.2 优化连接参数
yaml
spring:
datasource:
url: jdbc:kingbase8://localhost:54321/surveysystem?currentSchema=public&searchPath=public
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
关键参数说明:
currentSchema=public
:设置当前模式searchPath=public
:设置搜索路径- 这些参数对KingBase的模式管理至关重要
第三步:MyBatis Plus配置优化
3.1 创建KingBase专用配置
java
// KingBaseConfig.java
@Configuration
@ConditionalOnProperty(name = "spring.datasource.driver-class-name",
havingValue = "com.kingbase8.Driver")
public class KingBaseConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件 - 指定KingBase数据库类型
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.KINGBASE_ES));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
3.2 全局配置优化
yaml
# application.yml
mybatis-plus:
configuration:
database-id: kingbase
global-config:
db-config:
db-type: kingbase_es
id-type: auto
第四步:表名模式前缀处理
4.1 问题分析
KingBase数据库默认使用public
模式,所有表名前面都需要加上public.
前缀,否则会出现以下错误:
relation "sys_user" does not exist
4.2 解决方案一:修改Mapper XML文件
xml
<!-- 修复前 -->
<select id="selectUser" resultType="SysUser">
SELECT * FROM sys_user WHERE user_name = #{userName}
</select>
<!-- 修复后 -->
<select id="selectUser" resultType="SysUser">
SELECT * FROM public.sys_user WHERE user_name = #{userName}
</select>
需要修复的关键文件:
SysUserMapper.xml
- 用户相关查询SysRoleMapper.xml
- 角色相关查询SysMenuMapper.xml
- 菜单相关查询- 其他包含表名的Mapper文件
4.3 解决方案二:修改实体类注解
java
// 修复前
@TableName(value = "sys_user")
public class SysUser implements Serializable {
// ...
}
// 修复后
@TableName(value = "public.sys_user")
public class SysUser implements Serializable {
// ...
}
4.4 解决方案三:创建表名处理器
java
@Component
public class TableNameProcessor {
private static final String DEFAULT_SCHEMA = "public";
/**
* 处理SQL语句,为表名添加模式前缀
*/
public String processSql(String sql) {
if (sql == null || sql.trim().isEmpty()) {
return sql;
}
StringBuffer result = new StringBuffer();
Pattern pattern = Pattern.compile(
"\\b(FROM|from|JOIN|join|UPDATE|update|DELETE\\s+FROM|delete\\s+from|INSERT\\s+INTO|insert\\s+into)\\s+([a-zA-Z_][a-zA-Z0-9_]*)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
String keyword = matcher.group(1);
String tableName = matcher.group(2);
if (!tableName.contains(".")) {
matcher.appendReplacement(result, keyword + " " + DEFAULT_SCHEMA + "." + tableName);
}
}
matcher.appendTail(result);
return result.toString();
}
}
第五步:SQL关键字冲突处理
5.1 问题识别
level
是SQL关键字,在KingBase中会导致语法错误:
sql
-- 错误示例
SELECT * FROM sys_ad WHERE level = 1;
5.2 字段重命名
java
// 修复前
@ApiModelProperty("行政区划级别(0国家1省2市3县)")
private Integer level;
// 修复后
@ApiModelProperty("行政区划级别(0国家1省2市3县)")
private Integer levels;
5.3 批量更新引用
java
// 更新所有相关的方法调用
ad.setLevels(pAd.getLevels() + 1);
return lambdaQuery().eq(SysAd::getLevels, AdConstants.GJ).one();
第六步:数据库初始化配置
6.1 创建数据库配置类
java
@Component
public class KingBaseDatabaseConfig {
@PostConstruct
public void initDatabase() {
try (Connection conn = dataSource.getConnection()) {
// 设置搜索路径
try (Statement stmt = conn.createStatement()) {
stmt.execute("SET search_path TO public, \"$user\", public");
stmt.execute("SET default_tablespace TO ''");
}
} catch (SQLException e) {
log.error("初始化KingBase数据库配置失败", e);
}
}
}
6.2 数据库表结构迁移
sql
-- 创建模式(如果不存在)
CREATE SCHEMA IF NOT EXISTS public;
-- 设置默认模式
SET search_path TO public;
-- 重命名字段(如果需要)
ALTER TABLE public.sys_ad RENAME COLUMN level TO levels;
🚨 常见问题与解决方案
问题1:dbType not support : null
错误信息:
dbType not support : null, url jdbc:kingbase8://localhost:54321/surveysystem
解决方案:
- 在MyBatis Plus配置中明确指定数据库类型
- 使用
DbType.KINGBASE_ES
- 在配置文件中设置
db-type: kingbase_es
问题2:表不存在错误
错误信息:
relation "sys_user" does not exist
解决方案:
- 为所有表名添加
public.
前缀 - 修改实体类的
@TableName
注解 - 使用表名处理器动态处理
问题3:SQL关键字冲突
错误信息:
syntax error at or near "level"
解决方案:
- 识别SQL关键字字段
- 重命名字段(如
level
→levels
) - 更新所有相关引用
问题4:数据源配置错误
错误信息:
Could not bind properties under 'spring.datasource.druid'
解决方案:
- 检查Druid配置结构
- 确保属性名正确(
driver-class-name
而不是driverClassName
) - 验证配置文件的YAML语法
🧪 测试验证
1. 编译测试
bash
mvn clean compile
2. 启动测试
bash
mvn spring-boot:run
3. 功能测试
- 用户登录功能
- 数据库查询功能
- 分页查询功能
- 事务处理功能
4. 性能测试
- 连接池性能
- 查询响应时间
- 并发处理能力
📊 迁移效果对比
指标 | PostgreSQL | KingBase | 变化 |
---|---|---|---|
启动时间 | 15s | 18s | +20% |
查询性能 | 基准 | 95% | -5% |
内存占用 | 基准 | 110% | +10% |
兼容性 | 100% | 98% | -2% |
💡 最佳实践总结
1. 迁移前准备
- 完整备份原数据库
- 准备回滚方案
- 制定详细的迁移计划
- 准备测试环境
2. 迁移过程
- 逐步迁移,避免一次性大改动
- 保持代码版本控制
- 及时记录问题和解决方案
- 定期验证功能完整性
3. 迁移后优化
- 性能调优
- 监控告警配置
- 文档更新
- 团队培训
🔧 工具和脚本
1. 批量修复脚本
python
#!/usr/bin/env python3
# 批量修复Mapper XML文件中的表名
import os
import re
import glob
def fix_table_names_in_xml(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 修复表名模式
patterns = [
(r'\bFROM\s+sys_(\w+)', r'FROM public.sys_\1'),
(r'\bJOIN\s+sys_(\w+)', r'JOIN public.sys_\1'),
# 更多模式...
]
for pattern, replacement in patterns:
content = re.sub(pattern, replacement, content, flags=re.IGNORECASE)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
# 使用示例
mapper_files = glob.glob("src/main/resources/mapper/**/*.xml")
for file_path in mapper_files:
fix_table_names_in_xml(file_path)
2. 数据库连接测试
java
@Component
public class DatabaseConnectionTester {
@Autowired
private DataSource dataSource;
public void testConnection() {
try (Connection conn = dataSource.getConnection()) {
log.info("数据库连接成功: {}", conn.getMetaData().getDatabaseProductName());
// 测试查询
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT 1");
if (rs.next()) {
log.info("查询测试成功");
}
}
} catch (SQLException e) {
log.error("数据库连接测试失败", e);
}
}
}
📚 参考资料
🎉 总结
从PostgreSQL迁移到KingBase数据库是一个系统性的工程,需要从多个层面进行考虑和优化。通过本文的实战案例,我们可以看到:
- 依赖管理:正确配置驱动依赖
- 连接配置:优化数据库连接参数
- 框架适配:调整MyBatis Plus配置
- 表名处理:解决模式前缀问题
- 关键字冲突:避免SQL语法错误
- 测试验证:确保功能完整性
迁移过程中遇到的各种问题都有相应的解决方案,关键是要有系统性的思维和充分的准备。希望本文能为正在进行数据库国产化迁移的开发者提供有价值的参考。