懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)
需求描述
需求是这样的:因为我们目前的一个老项目是Oracle数据库的,这个库呢,数据库是没有注释的,而且字段名和表名都是大写风格,比如

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

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

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

这样的话呢,就会自动在我们指定的mysql数据库生成表了,不过,有两个瑕疵是:
- 生成的表,字段和表名都是和PO里一样的驼峰格式;
- 没有注释。
第一个问题,我这边是通过覆盖hibernate源码的方式解决,将驼峰转换为了下划线;
第二个问题,麻烦一些,因为要做到字段带注释的话,那就得看看哪里能拿到注释。hibernate执行过程中,从PO类里?不可能,编译好的class里,怎么会有注释呢?那就只能从源文件着手了,PO类的源码里,field上是有注释的,那就必须要去解析PO类的java文件,从里面提取出每个PO类中:字段--》注释的对应关系来。
大方向已定,我们开搞!
最后我这里解决这两个问题,是覆盖了三个hibernate的类的源码,大概如下:

在继续之前,先说明一下,这个肯定是要修改hibernate源码的,这里只讲讲怎么覆盖某个jar包里的类:
我这里是spring mvc的老项目,最后是部署在tomcat运行,tomcat的WebAppClassloader,负责加载以下两个路径的class:

覆盖的原理,就是依赖其查找class的先后顺序来做,比如lib下的某个jar包有:org.hibernate.mapping.Table这个类,正常情况下,都会加载到这个类;但如果我们在classes下放一个同包名同类名的类,那么就会优先加载我们的这个class了。但是假设这个类引用了hibernate的其他类B,不影响,毕竟我们没覆盖类B,所以还是会到lib下查找,最后还是会使用hibernate jar包中的B。
最终源码已经放在了:https://github.com/cctvckl/work_util/tree/master/Hibernate_PositiveEngineer
问题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包的源码):

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

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

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

到这里,基本搞定了第一个问题。
问题2解决步骤:给建表语句增加注释
其实这个步骤分成了2个小步骤,第一步是拿到下面这样的数据:

第二步,就是像上面第一步那样,在生成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里。
大致流程就是这样的,代码如下:
展开查看
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<String, HashMap<String, String>> commentMap = constructTableCommentMap();
System.out.println(JSON.toJSONString(commentMap));
} public static HashMap<String, HashMap<String, String>> constructTableCommentMap() {
HashMap<String, HashMap<String, String>> 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<String, HashMap<String, String>> 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<String> multiLineComments = new ArrayList<>();
int multiLineCommentsState = MULTI_COMMENT_NOT_START; boolean classStarted = false; ArrayList<FieldCommentVO> 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<String, String> 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方法:

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

总结
希望对大家有所帮助,有疑问可以直接加我。
源码在:https://github.com/cctvckl/work_util/tree/master/Hibernate_PositiveEngineer
懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)的更多相关文章
- scrapy实现自动抓取51job并分别保存到redis,mongo和mysql数据库中
项目简介 利用scrapy抓取51job上的python招聘信息,关键词为“python”,范围:全国 利用redis的set数据类型保存抓取过的url,现实避免重复抓取: 利用脚本实现每隔一段时间, ...
- [MySql]当虚拟机的IP地址自动更换后,JDBC使用原来的配置连不上MySql数据库时所报的异常。
Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. ...
- 在ThinkPHP框架(5.0.24)下引入Ueditor并实现向七牛云对象存储上传图片同时将图片信息保存到MySQL数据库,同时实现lazyload懒加载
这是我花了很多天的时间才得以真正实现的一组需求. 文章后面有完整Demo的GitHub链接. 一. 需求描述 1. 应用是基于ThinkPHP5开发的: 2. 服务器环境是LNMP,PHP版本是7.2 ...
- Guava中Lists.partition(List, size) 方法懒划分/懒分区
目录 Guava中Lists.partition(List, size) 方法懒划分/懒分区 背景 分析 总结 Guava中Lists.partition(List, size) 方法懒划分/懒分区 ...
- MySQL 数据库的tab 补全功能 (懒人必备)
MySQL 数据库的tab补全功能 跟着步骤走~~ 懒人养成第一步 不仅帮你补全 甚至预判你的预判,就问你可怕不可怕 1.安装相关依赖软件(需要配置yum官方 ...
- shell脚本每天自动备份mysql数据库
一.mysql提供了一个mysqldump的工具可以方便的导出导入数据库信息: 二.使用命令行shell测试执行mysqldump,理解必备的参数,查看生成的sql备份文件是否符合需求: /usr/b ...
- PHP是弱类型语言,自动转换,强制转换
强制转换: (int) - 转换成整型 (bool) - 转换.成布尔型 (float) - 转换成浮点型 (string) - 转换成字符串 (array) - 转换成数组 (object) - 转 ...
- Hibernate连接mysql数据库并自动创建表
天才第一步,雀氏纸尿裤,Hibernate第一步,连接数据库. Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个 ...
- paip.提高稳定性---自动检测sleep mysql数据库死连接以及kill
paip.提高稳定性---自动检测sleep mysql数据库死连接以及kill 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:ht ...
随机推荐
- CentOS7上OpenResty安装
1,OpenResty安装 通过repl源安装: sudo yum-config-manager --add-repo https://openresty.org/yum/cn/centos/Open ...
- 【Python爬虫】第四课(查询照片拍摄地址)
首先,要能够查询到照片地址,查询的照片必须要开GPS拍,且上传时用原图…… 查询图片的exif信息,使用exifread包 import exifread img = exifread.process ...
- 使用Hexo搭建个人博客并部署到GitHub或码云上全过程
一.前言 如上图所示:GitHub有Github Pages,而码云也有码云 Pages 1.Github Pages或Gitee Pages是什么呢? Github Pages或者Gitee Pag ...
- 目标检测中常提到的IoU和mAP究竟是什么?
看完这篇就懂了. IoU intersect over union,中文:交并比.指目标预测框和真实框的交集和并集的比例. mAP mean average precision.是指每个类别的平均查准 ...
- 使用命令行创建maven web项目
一,前言 遇到了一个非常简单的maven命令: 总的来说,这是一个用maven创建一个jersey快速开始项目的maven命令. 这次不妨使用maven命令来构建项目,看体验如何. 在文章idea m ...
- 性能测试瓶颈判断(LR&Windowns)
性能测试瓶颈判断(LR&Windowns) 一.判断CPU瓶颈(Processor) 1, %processor time 如果该值持续超过95%,表明瓶颈是CPU.可以考虑增加一个处理器或换 ...
- HNU_小初高数学学习软件_功能说明
结对编程项目主要功能: 1.用户注册功能.用户提供手机号码,点击注册将收到一个注册码,用户可使用该注册码完成注册: 2.用户完成注册后,界面提示设置密码,用户输入两次密码匹配后设置密码成功.密码6-1 ...
- 对Servlet执行流程的初步认识
这里我们以Post方式请求Serclet为例 1.找到 中的URL地址 Form表单的Post请求HelloServlet(Action="HelloServlet")发起时, A ...
- 使用Espresso测试记录
准备工作 建立测试项目 添加测试依赖 编写Espresso测试 运行测试并检查测试结果 建立测试项目 使用Android Studio建立测试项目,Activity模版使用 LoginActivity ...
- 使用 .gitignore 忽略 Git 仓库中的文件
.gitignore 在Git中,很多时候你只想将代码提交到仓库,而不是将当前文件目录下的文件全部提交到Git仓库中,例如在MacOS系统下面的.DS_Store文件,或者是Xocde的操作记录,又或 ...