国产适配之MySQL替换为达梦8数据库

守听  论坛元老 | 2024-9-10 05:31:12 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1027|帖子 1027|积分 3081

1. 背景

项目中要做国产化,MySQL要替换成达梦8数据库。项目中MySQL的建表语句和内置数据通过.sql文件维护,安装时会初始化表结构和表内置数据。项目架构为SpringBoot + JPA / Mybatis。适配工作内容包括数据库迁移、数据导出、项目中的设置更改和相干问题解决方案。
2. 数据处理流程

1. 前期装备

1. 安装达梦8数据库

达梦官网有提供安装包,根据本身的场景举行选择,linux_x86大概linux_aarch64,由于我们项目要全面国产化,以是服务器用的国产华为的鲲鹏服务器(aarch64),操纵系统为国产银河麒麟V10。安装步骤按官网提供的文档就行,下载后安装包里也会有一些PDF阐明文档可参考。
2. 创建库,启动

安装时假如选择了图形化界面安装,则有DM数据库设置助手工具,可用此工具来创建数据库实例,设置的话中心有个巨细写是否敏感设置,此设置默认选择不敏感,否则可能背面会有坑(背面说),安装时记得把客户端也选上,背面用其客户端举行操纵,其他设置的话默认就行。安装完成后其中默认用户为SYSDBA,默认端口为5236。linux环境创建数据库实例传送:dm实例创建步骤
2. 库数据处理

这一步的处理主要是将之前项目中存储的.sql文件中MySQL的表结构和表数据相干sql转换为达梦数据库所支持的sql,而且同样生存为.sql文件,后续项目运行之前直接用sql文件举行建表导数据等初始化操纵。大概头脑如下:
1.先把之前sql文件(MySQL)导入到MySQL数据库中
2.利用达梦的数据迁移工具把MySQL库中的数据迁移到达梦数据库中
3.利用达梦数据库迁移工具把达梦数据库中的数据导出到sql文件,此时sql文件中的sql语句就可在达梦数据库中执行
1. 数据迁移

假如安装时选择安装了客户端工具,则会天生一些客户端操纵工具,如迁移工具、DM管理工具、SQL交互式查询工具等。迁移时选择DM数据迁移工具,按照工具内的步骤,选择MySQL服务和数据库以及要迁移的DM数据库。

1.新建迁移,按需选择,我这边是MySQL -> DM。
2.选择数据源迁移时可以指定Mysql数据库的驱动,设置一下jdbc驱动和连接参数即可。达梦的话就是用默认驱动即可。

3.迁移计谋,可选择保持对象名巨细写,假如MySQL中表字段有用到json类型的字段时,必要手动设置一下类型映射关系,将JSON转成VARCHAR,并设置长度,因为达梦没不支持json类型,迁移时他会默认转成VARCHAR,但是长度会变得很大(具体忘记了),这时某些场景查询时会报错,设置成8188即可,按图设置即可


4.背面选择迁移模式的话全选即可,没什么必要特殊注意的点
2.数据导出

第三步迁移完成后,此时达梦数据库已经有和MySQL同名的库(dm中是schema概念)和表数据了。接下来要把库中的数据导出为.sql文件,到时候放到项目中安装时用来初始化表及数据。
此时仍然必要用达梦的数据迁移工具,新建数据迁移,选择数据迁移方式为DM -> SQL,然后指定必要迁移的数据源(达梦中的scheme),然后导出到目标文件即可。

3. 项目适配(重点)

1. 库名问题

问题:导出后的达梦sql脚本你会发现,建表语句格式为schema.table,而且主键自增关键字酿成了IDENTITY。项目中假如用SYSDBA用户连接大概别的用户连接时,执行sql语句都要加上schema(可以明白为mysql的库名,后续就说库名了),如select * from “MY_DB”.“T_USER_TEST”,如不加库名则会报错,当然不可能把项目中全部的sql都改一遍
  1. -- mysql
  2. CREATE TABLE `T_USER_TEST`
  3. (
  4. "id" BIGINT NOT NULL AUTO_INCREMENT,//主键自增
  5. "name" VARCHAR(255) NULL
  6. );
复制代码
  1. -- 达梦
  2. CREATE TABLE "MY_DB"."T_USER_TEST"
  3. (
  4. "id" BIGINT IDENTITY(1,2) NOT NULL,//主键自增
  5. "name" VARCHAR(255) NULL
  6. );
复制代码
解决方案:创建一个用户,用户名为库名,创建用户后达梦会主动创建一个和用户名相同的库,此时用此用户登录连接,执行sql语句时表名前面就不必要加库名了,因为他默认查的就是此用户下的库。语句如下(包括创建表空间、赋权等),后续连接时使用此账号和密码以及url连接中的schema(MY_DB)
  1. -- 创建表空间MY_DB
  2. CREATE tablespace MY_DB DATAFILE 'MY_DB.DBF' SIZE 128;
  3. -- 创建用户MY_DB,密码为123456,此时会自动创建名为MY_DB的schema
  4. CREATE USER "MY_DB" IDENTIFIED BY "123456" DEFAULT tablespace MY_DB;
  5. -- 为MY_DB用户赋权
  6. grant "DBA","RESOURCE","PUBLIC","SOI" to "MY_DB" with admin option;
  7. grant EXECUTE on "SYS"."DBMS_XMLGEN" to "MY_DB";
复制代码
Spring数据库连接设置参考:
  1. #dm8连接
  2. spring.datasource.url=jdbc:dm://127.0.0.1:5236/MY_DB
  3. spring.datasource.username=MY_DB
  4. spring.datasource.password=123456
  5. spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver
  6. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  7. #如果项目中有使用到JPA,参考如下方言配置
  8. spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.DmDialect
  9. spring.jpa.properties.hibernate.hbn2ddl.auto=none
  10. spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
复制代码
2. 主键自增问题

1. 问题剖析

起首,达梦数据库是支持主键自增的,DDL中自增关键字为IDENTITY,假如我们表中的id字段设置的为自增id,insert语法常见如下三种:
  1. -- 如下建表语句,id为自增id
  2. CREATE TABLE "MY_DB"."t_user_test"
  3. (
  4. "id" BIGINT IDENTITY(1,2) NOT NULL,//主键自增
  5. "name" VARCHAR(255) NULL
  6. );
  7. -- 1.insert的正确姿势,此时会生成则增id
  8. insert into "t_user_test"(name) values("tom");
  9. -- 2.错误示范,此时会报错:仅当指定列列表,且SET IDENTITY_INSERT为ON时,才能对自增列赋值
  10. insert into "t_user_test"(id,name) values(1,"tom");
  11. -- 3.错误示范,此时会报错: 仅当指定列列表,且SET IDENTITY_INSERT为ON时,才能对自增列赋值或者违反列[id]非空约束
  12. insert into "t_user_test"(id,name) values(null,"tom");
复制代码
1.第一种插入没问题,无可厚非
2.第二种插入会报错,意思就是说,你的id设置的为自增列,但是你插入时对自增列手动赋值,这是不答应的,设置了自增就应该用数据库的自增天生。但是项目中难免有手动设置id插入的场景,此时也是有解决方案的,就是在插入之前设置IDENTITY_INSERT为ON。注意IDENTITY_INSERT关键字是表级别的关键字,语法要指定到表,不能对全库举行设置。
  1. -- 设置t_user_test表
  2. SET IDENTITY_INSERT MY_DB.t_user_test ON
  3. insert into("id","name") values(1,"tom");
  4. -- OFF可以不执行,不影响
  5. SET IDENTITY_INSERT MY_DB.t_user_test OFF
复制代码
针对IDENTITY_INSERT问题,本人做了一些测试,得出以下结论供参考:


  • 如必要使用数据库主键自增特性,必要在主键列上声明IDENTITY
  • 当insert语句时,假如手动设置id值,则必要设置此表的IDENTITY_INSERT为 ON
  • 执行完不关闭(SET IDENTITY_INSERT MY_DB.t_user_test OFF),再次插入id为空的值还是可以自增的
  • 不同会话之间执行SET IDENTITY_INSERT MY操纵不会互相影响
  • 同一会话同一时候只能有一张表IDENTITY_INSERT 设置为ON,背面会覆盖前面的,同一会话多次设置只有末了一次设置见效
  • 当insert语句中,假如id显示插入,而且value为null,则会报非空约束的问题
开启语句:SET IDENTITY_INSERT db.table ON
关闭语句:SET IDENTITY_INSERT db.table OFF
3.第三种插入报错很明显,当你没有设置IDENTITY_INSERT时,他会先报错让你对其设置为ON,假如设置完后就会报错违背id非空约束,因为id建表时为主键,自带非空约束。不能显示插入null值,此种错误只能对sql举行处理,背面会讲。
2. 问题处理

经过以上问题分析,insert某张表时,可以先设置IDENTITY_INSERT为ON,固然只有第一种insert不必要设置,可以直接走自增,但是你设置后也不会影响insert的执行,为了偷懒不想整理项目中的sql,索性全部insert都设置IDENTITY_INSERT为ON。当然你可以写sql,修改项目中的代码,在全部insert操纵之前都执行一遍INDENTITY_INSERT ON,但是代码中持久层框架用了JPA和Mybatis,而且此类sql许多,以是采用AOP的方式解决。


  • JPA
解决思绪:在我们项目中使用JPA生存对象实现插入都是间接调用JpaRepository.save()方法,以是在此方法加一层拦截处理就行了,执行save之前先执行SET IDENTITY_INSERT ON,参考代码如下:
  1. @Aspect
  2. @Component
  3. public class JpaSaveAspect {
  4.     public static final String IDENTITY_INSERT_ON = "SET IDENTITY_INSERT MY_DB.%s ON";
  5.     public static final String IDENTITY_INSERT_OFF = "SET IDENTITY_INSERT MY_DB.%s OFF";
  6.     @Autowired
  7.     private JdbcTemplate jdbcTemplate;
  8.    // 节点为JpaRepository.save
  9.     @Pointcut("execution(* org.springframework.data.jpa.repository.JpaRepository.save(..))")
  10.     public void savePointcut() {
  11.     }
  12.    //执行切点方法之前要进行的处理
  13.     @Before("savePointcut()")
  14.     public void beforeSave(JoinPoint joinPoint) {
  15.         Object[] args = joinPoint.getArgs();
  16.         if (Objects.isNull(args) || args.length != 1) {
  17.             return;
  18.         }
  19.         Object obj = args[0];
  20.         Class<?> clazz = obj.getClass();
  21.         Annotation[] annotations = clazz.getAnnotations();
  22.         Long id = null;
  23.         try {
  24.             //通过反射获取save的实体对象,并通过getId方法获取里面的id值,也就是主键值
  25.             Method method = clazz.getMethod("getId");
  26.             id = (Long) method.invoke(obj);
  27.         } catch (Exception e) {
  28.         }
  29.         // 当id(主键)为空时,不需要处理,因为此时走的数据库的自增
  30.         if (Objects.isNull(id) || id <= 0){
  31.             return;
  32.         }
  33.         for (Annotation annotation : annotations) {
  34.             // 获取JPA实体的@Tabel注解,解析出表名
  35.             if (annotation instanceof Table) {
  36.                 Table tableAnnotation = (Table) annotation;
  37.                 //表名拼接进sql进行执行,SET IDENTITY_INSERT MY_DB.t_user ON
  38.                 String identityInsertOn = String.format(IDENTITY_INSERT_ON, tableAnnotation.name());
  39.                 log.warn("JPA IDENTITY_INSERT_ON:{}", identityInsertOn);
  40.                 jdbcTemplate.execute(identityInsertOn);
  41.             }
  42.         }
  43.     }
  44. }
复制代码


  • Mybatis
解决思绪:Mybatis提供的有本身的拦截器,也叫插件,只必要自定义拦截器即可,使用方式是实现org.apache.ibatis.plugin.Interceptor接口并注册为Bean,并在Mybatis的SqlSessionFactory设置此拦截器使其见效。对这块不熟的可以网上看看相干资料。接下来拦截器中就可以拦截sql并在sql执行之前做处理了。参考代码如下:
代码处理的问题:
1.处理非法字符,如删掉sql中的`字符
2.处理boolean参数,达梦的bit类型对应java中的boolean类型,把sql中的true和false关键字替换为1和0
3.处理主键自增
在执行insert之前执行SET IDENTITY_INSERT,由于本人对Mybatis不太熟,没在拦截器中找到sqlSersion对象,也就没法通过sqlSersion来执行我自定义的sql。而通过调用jdbcTemplate等三方执行,可能导致两个sql不在一个会话中执行,也就导致可能你执行的SET IDENTITY_INSERT不在此会话见效(看上面IDENTITY_INSERT的测试结果),此时你可以通过在insert 语句所在的方法加事务实验解决。目前我是通过拼接sql方式解决,在拦截器中把剖析出来的sql前面拼接自定义sql。(会话的问题和事务我也只是猜测,并没实际验证,仅供参考)
  1. /**
  2. * @description: mybatis sql拦截器,作用有三种:1.处理非法字符 2.处理boolean参数 3.处理插入主键自增问题
  3. */
  4. @Intercepts({
  5.         @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
  6. })
  7. @Slf4j
  8. @Component
  9. public class MybatisSqlInterceptor implements Interceptor {
  10.     /**
  11.      * 正则不区分大小写匹配"=true",包括=中间有空白字符
  12.      */
  13.     private static final String SQL_TRUE_PARAM_REG = "(?i)=\\s*true";
  14.     /**
  15.      * 正则不区分大小写匹配"=false",包括=中间有空白字符
  16.      */
  17.     private static final String SQL_FALSE_PARAM_REG = "(?i)=\\s*false";
  18.     /**
  19.      * 正则匹配insert into和merge into语句
  20.      */
  21.     private static final String SQL_INSERT_REG = "(?i)(insert into|merge into)\\s+([^\\s]+)";
  22.     /**
  23.      * 开启insert开关
  24.      */
  25.     private static final String IDENTITY_INSERT_ON = "SET IDENTITY_INSERT MY_DB.%s ON;";
  26.     /**
  27.      * 无主键的关联表
  28.      */
  29.     private Set<String> identityInsertExcludeTableSet;
  30.         //配置,可配置库中无自增键的表,把它过滤掉,因为这些表没有主键自增问题
  31.     @Value("${mybatis.insert.exclude.table:t_no_identity_table_test}")
  32.     private String excludeTable;
  33.     @PostConstruct
  34.     public void initExcludeTableSet() {
  35.         //加载时将excludeTable的表放入HaseSet,提升后续匹配效率
  36.         identityInsertExcludeTableSet = Arrays.stream(excludeTable.split(","))
  37.                                               .collect(Collectors.toSet());
  38.     }
  39.     @Override
  40.     public Object intercept(Invocation invocation) throws Exception {
  41.         StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
  42.         MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
  43.         MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
  44.         SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
  45.         BoundSql boundSql = statementHandler.getBoundSql();
  46.         String sql = this.handleIllegalChar(boundSql.getSql());
  47.         if (sqlCommandType == SqlCommandType.SELECT) {
  48.             sql = this.handleBooleanParam(sql);
  49.         }
  50.         if (sqlCommandType == SqlCommandType.INSERT) {
  51.             sql = this.handleIdentityInsertOn(sql);
  52.         }
  53.         metaObject.setValue("delegate.boundSql.sql", sql);
  54.         return invocation.proceed();
  55.     }
  56.     /**
  57.      * 处理非法字符
  58.      *
  59.      * @param sql
  60.      * @return: java.lang.String
  61.      */
  62.     private String handleIllegalChar(String sql) {
  63.         return sql.replace("`", "");
  64.     }
  65.     /**
  66.      * 处理插入时自增id开关问题
  67.      *
  68.      * @param sql
  69.      */
  70.     private String handleIdentityInsertOn(String sql) {
  71.         String tableName = null;
  72.         Pattern pattern = Pattern.compile(SQL_INSERT_REG, Pattern.CASE_INSENSITIVE);
  73.         Matcher matcher = pattern.matcher(sql);
  74.         if (matcher.find()) {
  75.             tableName = matcher.group(2);
  76.         }
  77.         if (StringUtils.isNotBlank(tableName)
  78.                 && !identityInsertExcludeTableSet.contains(tableName)) {
  79.             String identityInsertOn = String.format(IDENTITY_INSERT_ON, tableName);
  80.             log.warn("Mybatis IDENTITY_INSERT_ON:{}", identityInsertOn);
  81.             sql = identityInsertOn + sql;
  82.         }
  83.         return sql;
  84.     }
  85.     /**
  86.      * 处理sql中的布尔值
  87.      *
  88.      * @param sql
  89.      * @return: java.lang.String
  90.      */
  91.     private String handleBooleanParam(String sql) {
  92.         return sql.replaceAll(SQL_TRUE_PARAM_REG, "= 1")
  93.                   .replaceAll(SQL_FALSE_PARAM_REG, "= 0");
  94.     }
  95. }
复制代码
3. SQL语法相干问题

安装时能要求巨细写不敏感只管选择巨细写不敏感,不然建表时字段都要用大写,假如用小写,查询时字段用小写查可能会报错:无效列名等
1. 字段column

字段名称关键字冲突

数据库中都有一些本身的关键字,假如建的表中有些关键字和数据库中的冲突,就有可能执行某些sql报错,此类冲突的关键字只管手动改掉,以下是我遇到的关键字供参考:
logic、comment、domain
字段类型

1)mysql中的json类型对应dm8中的varchar类型,但只管指定巨细,不然聚合查询可能报错
2)bit类型Mysql可使用true和false举行查询,dm只能使用0和1查询
2. 函数及语法

1.GROUP_CONCAT语法要换成WM_CONCAT(其它函数可自行百度,资料许多,也可参考oracle语法)
2.假如用到group by,则select的列必须都是分组内的,报错参考:不是 GROUP BY 表达式。
可根据场景看看是否能删除group by替换为select DISTINCT xxx等
3.select DISTINCT对字段去重时,去重字段中不能有blob大概clob,如text类型的字段,也就是不能把text 类型的字段放到DISTINCT背面,报错参考:试图在blob大概clob列上排序或比较
4.假如使用到mysql的on duplicate key,在达梦8中可以用MERGE INTO语法举行替换(mybatis中批量插 入更新)。
5.if语句,达梦支持if语句,但只能支持简朴的场景,如下
  1. -- 支持
  2. where if(id>1,2,3)
  3. -- 不支持
  4. where if(1 = 1,status = 2 or status =3 ,1=1)
复制代码
以上不支持的场景可以用逻辑解决:
  1. where ((1 = 1 AND (status = 2 OR status = 3)) OR (1 = 1))
复制代码
4. 其它问题

我们项目中用到了clickhouse数据库,而且使用了clickhouse的字典表连接了外部数据库,也就是Mysql中的某些表,作用是可以吧mysql某些表里的某些数据同步到clickhouse映射表中,而且创建好映射表后,后期clickhouse中表的数据可主动同步mysql表中的数据,如不了解的可去ck官网查看 https://clickhouse.com/docs/zh/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources#dicts-external_dicts_dict_sources-mysql
问题:clickhouse内置支持mysql的字典表,但不支持达梦8,
解决:clickhouse提供了bridge方式,如clickhouse-jdbc-bridge、clickhouse-odbc-bridge,大概意思就是提供了个中心件,它是以独立历程来启动,他来作为ck和外部数据库的桥接来主动同步数据。
1. ODBC

环境依赖:unixODBC + 达梦8的odbc驱动
其中unixODBC可根据操纵系统下载rpm包大概下载源码举行编译(网上有教程)
odbc驱动可以从安装达梦8所在的服务器上找,安装目录下有个drivers文件,内里有各种驱动,包括odbc,把驱动文件(.so)以及相干依赖拷贝到ck服务器,然后在unixODBC的设置文件中添加dm的数据源和驱动设置路径,然后再ck中创建字典表,而且指定达梦数据源。
这种方式本人在x86机器验证过,是可行的,但是unixODBC有版本问题,达梦8odbc驱动是.so文件链接库,同时有依赖其他链接库,操纵不好就会有链接缺失的问题。本人就是背面x86验证后,拿到aarch64架构机器去验证时,依赖的加解密so库和系统中内置的冲突了,但是又没找到法子对其举行环境隔离,故背面放弃了。
2.JDBC

实际中本人是采用这种方式,开始没采用是因为当时看到了jdbc-bridge,但还是想找一种字典表的方式,想着看看字典表支不支持设置自定义连接数据源,就越走越远,背面又用了ODBC开始踩坑,不停踩到国产环境编译动态库后冲突问题,作为java步伐员已经走不动了,就蓦地回首从0开始踩坑jdbc,中心jdbc还有一些踩坑历程就不说了,下面直接说结论吧。
clickhouse-jdbc-bridge源码地点:https://github.com/ClickHouse/clickhouse-jdbc-bridge
源码地点有阐明,因为是采用java代码编写的,以是没有跨平台的问题,直接下载rpm包安装即可:
  1. wget https://github.com/ClickHouse/clickhouse-jdbc-bridge/releases/download/v2.1.0/clickhouse-jdbc-bridge-2.1.0-1.noarch.rpm
  2. rpm -ivh clickhouse-jdbc-bridge-2.1.0-1.noarch.rpm
复制代码
1.安装后必要把达梦8的jdbc驱动放入某个文件,背面设置要指定此驱动
2.在jdbc-bridge的安装目录(默认为/etc/clickhouse-jdbc-bridge/config/datasources),新建.json文件,内里设置你的数据库相干连接(驱动、url、账号、密码等信息)
3.运行启动clickhouse-jdbc-bridge(默认端口9019)
3.设置clickhouse的config.xml文件,设置jdbc-bridge连接,重启clickhouse服务
  1. <jdbc_bridge>
  2.         <host>127.0.0.1</host>
  3.         <port>9019</port>
  4. </jdbc_bridge>
复制代码
4.在clickhouse客户端执行建表语句,示比方下,其中dm8参数是clickhouse-jdbc-bridge数据源设置的名称,DM_DB是达梦数据库的schema名,背面是将查询的结果放入ck表,这个位置也可以直接写表名。
  1. CREATE TABLE ck_user_test (
  2.     id UInt64,
  3.     name String
  4. )
  5. ENGINE = JDBC('dm8', 'DM_DB', 'select id,name from t_dm_user_test WHERE xxx=0')
复制代码
建表成功后再ck中就可以查询ck_user_test这张表了,数据同步周期可设置,具体其他设置可参考官网或自行百度

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表