Mysql 迁移到 kingbase / PostGreSQL 数据库的项目改造(项目实战)

1 文章内容发布版本记录

  • 2023-05-16 创建文章内容
  • 2023-08-08 更新被坑的内容
  • 2023-08-17 调整文章呈现格式(分类展示)

2 背景

  • 公司最近要求国产化改造,需要把 mysql 数据迁移到 kingbase;
  • 由于项目架构是之前做C语言的人设计,所以很多表字段都存在直接使用驼峰式命名的情况,而不是我们所熟悉的 “数据库是下划线,实体类采用驼峰式” 形式命名,这部分也需要改造;
  • 目前碰到了这些坑,整理了下发出来,希望能给后续有需要的人提供参考价值;
  • 我们项目共计改造10个工程,5个后端开发完成国产化改造,共花费4天左右时间(含自测,不含集成测试);
  • 后续会持续更新,查缺补漏... ...

( Kingbase 是基于 PostGreSQL 开发的一款数据库,所以本篇文章的大部分内容也适用于 PostGreSQL 改造 )

3 应用以及版本

  • MybatisPlus(3.3.1)
  • kingbaseES 数据库版本(V008R006C007B0024)
  • kingbase驱动包版本(8.6.0)
  • mysql 数据库(8.0.31)
  • mysql 驱动包版本(8.0.20)

4 主要改造点

  • 数据库连接方式;
  • pom.xml 依赖包修改;
  • 数据库改造点;
  • mybatis: *Mapper.xml 文件改造;
  • java 类改造(含实体类、Service和Controller等);

5 项目改造内容

5.1 数据库连接方式(含改造前后配置)

###【改造前的数据库连接配置】
spring:
  datasource:
  	## 涉及敏感内容,需要将 IP、端口、数据库名修改成你自己想要的
    url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
    ## 涉及敏感内容,将数据库用户名、密码修改成你自己想要的
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 1
      connection-test-query: SELECT 1 FROM DUAL
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 30000
      pool-name: GHHikariCP
      max-lifetime: 120000
      connection-timeout: 30000
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: false

###【改造后的数据库连接配置】
spring:
  datasource:
  	## 涉及敏感内容,需要将 IP、端口、数据库名修改成你自己想要的
    url: jdbc:kingbase8://127.0.0.1:54321/test_db?useUnicode=true&characterEncoding=UTF-8
    ## 涉及敏感内容,将数据库用户名、密码修改成你自己想要的
    username: system
    password: system@123
    driver-class-name: com.kingbase8.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 1
      connection-test-query: SELECT 1
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 30000
      pool-name: GHHikariCP
      max-lifetime: 120000
      connection-timeout: 30000
      db-type: postgresql
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
  	## 这个配置是用来忽略大小写
    map-underscore-to-camel-case: true
  ## 可选配置,自定义处理类,处理数据库中 Boolean 和 实体类中的 Integer 的类型转换,具体见下文 5.4 内容。
  type-handlers-package: com.gh.common.handler.common

5.2 pom.xml 依赖包修改

(注意:kingbase 的驱动包,在阿里仓库是没有的,需要到官网上下载下来放到本地仓库中,我用的是公司仓库)

<!--mysql 驱动包-->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>

<!--KingBase 驱动包-->
<dependency>
  <groupId>com.xh</groupId>
  <artifactId>kingbase-x86</artifactId>
  <version>8.6.0</version>
</dependency>


5.3 数据库改造点

5.3.1 数据库表字段如果是char类型且长度大于1,如果存储的数据小于char类型长度,建议改为varchar
5.3.2 数据库 bit 类型统一修改为int4
5.3.3 创建若干函数,例如:date_format、exec 等

(很多 mysql 函数不被人大金仓数据库支持,需要手动创建)

-- 创建 date_format 函数,并支持若干日期格式
-- kingbase 没有这个函数,需要自定义,以下为参考,具体还需要结合业务看是否适配,可自行增减
CREATE OR REPLACE FUNCTION "public"."date_format"("indate" anyelement, "intext" text)
    RETURNS "pg_catalog"."text" AS $BODY$
	BEGIN
		IF upper(inText) = upper('%Y%m%d_%H%i') THEN
		return to_char(inDate,'YYYYMMDD_HH24MI');
		END IF;
		IF upper(inText) = upper('%Y%m%d%H%i%s') THEN
		return to_char(inDate,'YYYYMMDDHH24MISS');
		END IF;
		IF upper(inText) = upper('%Y-%m-%d %H') THEN
		return to_char(inDate,'YYYY-MM-DD HH24');
		END IF;
		IF upper(inText) = upper('%Y-%m-%d') THEN
		return to_char(inDate,'YYYY-MM-DD');
		END IF;
		IF upper(inText) = upper('%Y-%m') THEN
		return to_char(inDate,'YYYY-MM');
		end if;
		IF upper(inText) = upper('%Y') THEN
		return to_char(inDate,'YYYY');
		end if;
		IF upper(inText) = upper('%m%d') THEN
		return to_char(inDate,'MMDD');
		END IF;
		IF upper(inText) = upper('%k') THEN
		return to_char(inDate,'HH24');
		END IF;
		IF upper(inText) = upper('%c') THEN
		return to_char(inDate,'MM');
		END IF;
		return '';
	END;
	$BODY$
LANGUAGE plpgsql VOLATILE
COST 100

-- 创建 exec 函数
CREATE OR REPLACE FUNCTION "public"."exec"("sqlstring" varchar)
  RETURNS "pg_catalog"."varchar" AS $BODY$
    declare
        res varchar(50);
    BEGIN
        EXECUTE sqlstring;
        RETURN 'ok';
    END
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100
  
5.3.4 检查各表、表字段名,统一修改为小写
5.3.5 检查各字段名是否存在驼峰式命名,统一修改为下划线的形式,并与对应的实体类一一对应——>即带下划线的数据库表字段对应驼峰命名的实体类字段

5.4 mybatis: *Mapper.xml 文件改造

5.4.1 凡是碰到 limit 关键字,需按照 postgre 语法重新更改
-- 对已知表查询前五条数据
修改前:limit 1,5
修改后:limit 0 offset 5
5.4.2 逐个检查 java 中 *Mapper.xml 文件中的是否有使用函数的形式,查看该函数是否被支持, mysql 中的很多函数无法在 kingbase / PostGreSQL 数据库中使用
5.4.3 逐个检查 java 中 *Mapper.xml 文件中的 update 语句,update 条件不能使用别名操作
修改前:update t_device t set t.band_flag = #{bandFlag} where t.id = #{id} ;
修改后:update t_device set band_flag = #{bandFlag} where id = #{id} ;
5.4.4 关联表字段存在类型不一致的情况,需要用 :: varchar 做类型转换,因为 mysql 的语法对字符串和数值类型做了兼容处理,而 kingbase 是严格区分字符串和数值,所以在遇到一些类型不一致的情况,需要类型转换
-- 已知
SELECT * FROM t_alarm_history h 
	LEFT JOIN t_gis_config c on c.device_id=d.id or c.device_id :: varchar = CONCAT('cam_',d.id);
5.4.5 sql查询过程中自定义的别名不要出现数据库关键字。例:select 字段名 as year (多见于统计年份)
5.4.6 kingbase数据库char存储是按照定长存储,不够的自动填充空字符,sql查询出来之后会带上缺少的空字符,比对的时候会出问题,变长字符串没有这个问题
5.4.7 kingbase 分组查询不能将上级查询结果放到子查询中作为条件
5.4.5 若该方法的返回类型为 resultMap ,则需要看情况修改该 resultMap 映射的 column 中的字段名;
5.4.6 需要注意的一点在mysql中排序的时候null默认是最小的,在kingbase里面null默认是排序最大值;
5.4.7 sql中包含 ` 符号需要替换掉,kingbase不支持这种写法,全部替换为空,或者替换成 ’

5.5 java 类改造(含实体类、Service和Controller等)

5.5.1 修改 @TableName 注解,将注解内的表名统一修改为小写,若表名是按照驼峰命名规则,建议改为带下划线形式
-- 已知表命名为 t_device 的情况下,以下为错误实例:
1@TableName("T_DEVICE")
2@TableName("tDevice")
3@TableName("T_device")
5.5.2 对于表自增的 id,应当加上注解:@TableId(type = IdType.AUTO) ,不要使用 @TableId(type = IdType.NONE) 注解,否则 id 不会自增长
5.5.3 检查实体类中的字段是否有非驼峰命名,驼峰命名不需要任何修改,最主要的是需要跟数据库中的字段保持对应,即带下划线的数据库表字段对应驼峰命名的实体类字段
--修改前的建表语句:
CREATE TABLE "public"."t_device" (
	"manualPath" varchar(64) COLLATE "pg_catalog"."default"
);
-- 修改前对应的实体类字段申明:
private String manualPath ;

-- 修改后的建表语句:
CREATE TABLE "public"."t_device" (
	"manual_path" varchar(64) COLLATE "pg_catalog"."default"
);

原理:(MybatisPlus 会自动将数据库中带下划线的字段转换成驼峰形式)

5.5.4 检索工程中使用 Wrapper 形式写 sql 语句或者字段的部分,必须将条件中的字段名称与表字段保持相同。Wrapper 中使用的字段,必须要和数据库保持一致,否则会报字段找不到的错误
5.5.5 若实体类中 username 字段没有按驼峰命名,需要在该字段上加上注解@TableField(“user_name”) 其他字段类似,最好保持一致,使用 userName 驼峰式命名规则
5.56 使用 @MapKey 注解的地方,需要将 key 统一为小写,金仓数据库查询出来无视大小写,统一会被自动转换成小写

sql 如下:

--【修改前】
SQL语句:select name as deviceName , id as deviceId from t_device 
java代码:Map deviceMap = (Map)deviceMap.get("deviceName");

--【修改后】
SQL语句:select name as devicename , id as deviceid from t_device 
java代码:Map deviceMap = (Map)deviceMap.get("devicename");
5.5.7 新增 BooleanTypeHandler 类,处理字段在数据库是 Boolean 类型,但是在映射实体类是 Integer 类型的转换问题
package com.gh.common.handler.common;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BooleanTypeHandler  extends BaseTypeHandler<Boolean> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Boolean aBoolean, JdbcType jdbcType) throws SQLException {
        if (aBoolean){
            ps.setInt(i,1);
        }else ps.setInt(i,0);
    }

    @Override
    public Boolean getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        int man = resultSet.getInt(columnName);
        return man == 1 ? true : false;
    }

    @Override
    public Boolean getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        int man = resultSet.getInt(columnIndex);
        return man == 1 ? true : false;
    }

    @Override
    public Boolean getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        int man = callableStatement.getInt(columnIndex);
        return man == 1 ? true : false;
    }
}

你可能感兴趣的:(数据库,数据库,mysql,java)