正向生成数据库mysql_懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)...

本文介绍了如何使用Hibernate正向工程将Oracle数据库转换为MySQL,同时解决了表名和字段名从驼峰式转换为下划线格式,并保留字段注释的问题。作者通过覆盖Hibernate源码,实现了从Java POJO类中提取注释并应用到MySQL建表语句中。文章详细阐述了整个过程,包括解析Java源文件提取注释、修改Hibernate生成建表语句的逻辑等步骤。
摘要由CSDN通过智能技术生成

需求描述

需求是这样的:因为我们目前的一个老项目是Oracle数据库的,这个库呢,数据库是没有注释的,而且字段名和表名都是大写风格,比如

65dd3f50d766d81aa68ebb340ed23d38.png

在代码层面的po呢,以前也是没有任何注释的,但是经过这些年,大家慢慢踩坑多了,也给po加上了一些注释了,比如:

26ab6267d400d03f2d0ab8b85288cc1b.png

现状就是这样,再说说目标是:希望把这个库能转成mysql,表名和字段名最好都用下划线分隔每个单词,字段呢,最好能有注释。也就是差不多下面这样:

013b16efc16229816bbc8bbeccdbfd93.png

方案分析

最早我尝试的就是hibernate正向工程,建一个空的mysql库,然后配置hibernate的选项为:

da264a971a4812234b6ffbfe47fefa81.png

这样的话呢,就会自动在我们指定的mysql数据库生成表了,不过,有两个瑕疵是:

生成的表,字段和表名都是和PO里一样的驼峰格式;

没有注释。

第一个问题,我这边是通过覆盖hibernate源码的方式解决,将驼峰转换为了下划线;

第二个问题,麻烦一些,因为要做到字段带注释的话,那就得看看哪里能拿到注释。hibernate执行过程中,从PO类里?不可能,编译好的class里,怎么会有注释呢?那就只能从源文件着手了,PO类的源码里,field上是有注释的,那就必须要去解析PO类的java文件,从里面提取出每个PO类中:字段--》注释的对应关系来。

大方向已定,我们开搞!

最后我这里解决这两个问题,是覆盖了三个hibernate的类的源码,大概如下:

a454b531fd4dda6f3118747fb2317a7a.png

在继续之前,先说明一下,这个肯定是要修改hibernate源码的,这里只讲讲怎么覆盖某个jar包里的类:

我这里是spring mvc的老项目,最后是部署在tomcat运行,tomcat的WebAppClassloader,负责加载以下两个路径的class:

0c76d3bf769bb3c2fbb651fea004939a.png

覆盖的原理,就是依赖其查找class的先后顺序来做,比如lib下的某个jar包有:org.hibernate.mapping.Table这个类,正常情况下,都会加载到这个类;但如果我们在classes下放一个同包名同类名的类,那么就会优先加载我们的这个class了。但是假设这个类引用了hibernate的其他类B,不影响,毕竟我们没覆盖类B,所以还是会到lib下查找,最后还是会使用hibernate jar包中的B。

问题1解决步骤:驼峰格式的建表语句转下划线

知道怎么覆盖了,再说说怎么去找要覆盖哪儿,这个需要一点经验。我这里先还原成没修改时的样子,跑一下项目,发现日志有以下输出:

2019-10-23 13:47:11.819 [main] DEBUG [] org.hibernate.SQL -

drop table if exists KPIRECORD

2019-10-23 13:47:11.823 [main] DEBUG [] org.hibernate.SQL -

create table KPIRECORD (

kpiRecordId varchar(255) not null,

endTime varchar(255),

evaluatorId varchar(255),

kpiComment varchar(255),

kpiDate datetime,

kpiValue double precision,

roleCode integer,

startTime varchar(255),

superiors varchar(255),

userId varchar(255),

primary key (kpiRecordId)

)

2019-10-23 13:47:11.988 [main] INFO [] org.hibernate.tool.hbm2ddl.SchemaExport - HHH000230: Schema export complete

其他不重要,我们看最后一行,里面包含了Schema export complete,这个肯定是代码里的日志,我们拿这个东西,在代码里搜一波(这一步,要求maven是下载了jar包的源码):

a9cfb35b94610ab380a991a92f343b0a.png

接下来,我们点进去,因为maven下载了源码的关系,所以再利用idea的findUsage功能,剩下的,就是在觉得比较靠谱的地方打上断点,运行一下,debug一下,大概就知道流程了。

ddd5128b92ff94c195b840b19059299a.png

找啊找,找到了下面的地方,(org.hibernate.mapping.Table#sqlCreateString)

c87fe9c2aeefb40abebd859fd2217dfd.png

怎么覆盖,不用多说了吧,如果是spring mvc(或者spring boot)架构,都要在最上层的module里的src下操作,加上这么一个全路径一致的类,然后将里面的sqlCreateString改写。

我这里附上改写后的:

93972d1b2bc513e926ea7775807f43e7.png

到这里,基本搞定了第一个问题。

问题2解决步骤:给建表语句增加注释

其实这个步骤分成了2个小步骤,第一步是拿到下面这样的数据:

cdcb0df49d08ca137fa84c8bbbdc4c41.png

第二步,就是像上面第一步那样,在生成create table语句时,根据table名称,取到上面这样的数据,然后再根据列名,取到注释,拼成一条下面这样的(重点是下面加粗部分):

start_time varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '考评开始时间',

问题2解决步骤之第一步:获取表字段注释

这部分纯粹考验字符串解析功力了,我说下思路,也可以直接看源码。主要是逐行读取java文件,然后看该行是否为注释(区分单行注释和多行注释):

单行:

/** 被考评人*/

private String userId;

多行:

/**

* 主键,考评记录ID

*/

private String kpiRecordId;

单行注释的话,直接用正则匹配;多行的话,会引入一个状态变量,最后还是会转换为一个单行注释。

匹配上后,提取出注释,存到一个全局变量;如果下一行正则匹配了一个field,则将之前的注释和这个field凑一对,存到map里。

大致流程就是这样的,代码如下:

展开查看

```java

package com.ceiec.util;

import com.alibaba.fastjson.JSON;

import java.io.*;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

/**

* @program: Product

* @author: Mr.Fyf

* @create: 2018-05-30 16:30

**/

public class CommentUtil {

/**

* 类名正则

*/

static Pattern classNamePattern = Pattern.compile(".*\\s+class\\s+(\\w+)\\s+.*\\{");

/**

* 单行注释

*/

static Pattern singleLineCommentPattern = Pattern.compile("/\\*\\*\\s+(.*)\\*/");

/**

* field

*/

static Pattern fieldPattern = Pattern.compile("private\\s+(\\w+)\\s+(.*);");

private static final int MULTI_COMMENT_NOT_START = 0;

private static final int MULTI_COMMENT_START = 1;

private static final int MULTI_COMMENT_END = 2;

public static void main(String[] args) throws IOException {

HashMap> commentMap = constructTableCommentMap();

System.out.println(JSON.toJSONString(commentMap));

}

public static HashMap> constructTableCommentMap() {

HashMap> tableFieldCommentsMap = new HashMap<>();

File dir = new File("F:\\workproject_codes\\bol_2.0_from_product_version\\CAD_Model\\src\\main\\java\\com\\ceiec\\model");

File[] files = dir.listFiles();

try {

//

for (File fileItem : files) {

processSingleFile(fileItem,tableFieldCommentsMap);

}

// File fileItem = new File("F:\\workproject_codes\\bol_2.0_from_product_version\\SYS_Model\\src\\main\\java\\com\\ceiec\\scm\\model\\ConsultingParentType.java");

// processSingleFile(fileItem, tableFieldCommentsMap);

} catch (Exception e) {

}

return tableFieldCommentsMap;

}

public static void processSingleFile(File fileItem, HashMap> tableFieldCommentsMap) throws IOException {

FileReader reader = null;

try {

reader = new FileReader(fileItem);

} catch (FileNotFoundException e) {

e.printStackTrace();

}

BufferedReader bufferedReader = new BufferedReader(reader);

String line = null;

ArrayList multiLineComments = new ArrayList<>();

int multiLineCommentsState = MULTI_COMMENT_NOT_START;

boolean classStarted = false;

ArrayList list = new ArrayList<>();

String className = null;

String lastSingleLineComment = null;

while ((line = bufferedReader.readLine()) != null) {

Matcher matcher = classNamePattern.matcher(line);

boolean b = matcher.find();

if (b) {

className = matcher.group(1);

classStarted = true;

continue;

}

if (!classStarted) {

continue;

}

if (line.contains("serialVersionUID")) {

continue;

}

if (multiLineCommentsState == MULTI_COMMENT_NOT_START) {

if (line.trim().equals("/**")) {

multiLineCommentsState = MULTI_COMMENT_START;

continue;

}

}

if (multiLineCommentsState == MULTI_COMMENT_START) {

multiLineComments.add(line);

if (line.trim().equals("*/") || line.trim().contains("*/")) {

for (String multiLineComment : multiLineComments) {

if (multiLineComment.trim().equals("/**") || multiLineComment.trim().equals("*/")) {

continue;

}

if (lastSingleLineComment == null) {

lastSingleLineComment = multiLineComment;

} else {

lastSingleLineComment = multiLineComment + lastSingleLineComment;

}

}

lastSingleLineComment = lastSingleLineComment.replaceAll("/", "").replaceAll("\\*", "").replaceAll("\\t", "");

multiLineComments.clear();

multiLineCommentsState = MULTI_COMMENT_NOT_START;

continue;

}

continue;

}

Matcher singleLineMathcer = singleLineCommentPattern.matcher(line);

boolean b1 = singleLineMathcer.find();

if (b1) {

lastSingleLineComment = singleLineMathcer.group(1);

continue;

}

Matcher filedMatcher = fieldPattern.matcher(line);

boolean b2 = filedMatcher.find();

if (b2) {

String fieldName = filedMatcher.group(2);

if (lastSingleLineComment != null) {

FieldCommentVO vo = new FieldCommentVO(fieldName, lastSingleLineComment);

list.add(vo);

lastSingleLineComment = null;

}

}

}

if (list.size() == 0) {

return;

}

HashMap fieldCommentMap = new HashMap<>();

for (FieldCommentVO fieldCommentVO : list) {

fieldCommentMap.put(fieldCommentVO.getFieldName().toLowerCase(), fieldCommentVO.getComment().trim());

}

tableFieldCommentsMap.put(className.toUpperCase(), fieldCommentMap);

}

}

```

问题2解决步骤之第二步:覆盖hibernate源码,建表过程中构造注释

这次覆盖了org.hibernate.cfg.Configuration#generateSchemaCreationScript方法:

42376fafd0616941a6c30a3f07e6912e.png

然后里面的内容也不用我细说了,再次根据列名查找注释,构造建表sql就行了。

这里加个成果展示:

e5ea120131d0e4ffa8cf89b7dc9e670f.png

总结

希望对大家有所帮助,有疑问可以直接加我。

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)]http://www.zyiz.net/tech/detail-93353.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值