需求是这样的:由于咱们目前的一个老项目是Oracle数据库的,这个库呢,数据库是没有注释的,并且字段名和表名都是大写风格,好比java
在代码层面的po呢,之前也是没有任何注释的,可是通过这些年,你们慢慢踩坑多了,也给po加上了一些注释了,好比:mysql
现状就是这样,再说说目标是:但愿把这个库能转成mysql,表名和字段名最好都用下划线分隔每一个单词,字段呢,最好能有注释。也就是差很少下面这样:git
最先我尝试的就是hibernate正向工程,建一个空的mysql库,而后配置hibernate的选项为:github
这样的话呢,就会自动在咱们指定的mysql数据库生成表了,不过,有两个瑕疵是:spring
第一个问题,我这边是经过覆盖hibernate源码的方式解决,将驼峰转换为了下划线;sql
第二个问题,麻烦一些,由于要作到字段带注释的话,那就得看看哪里能拿到注释。hibernate执行过程当中,从PO类里?不可能,编译好的class里,怎么会有注释呢?那就只能从源文件着手了,PO类的源码里,field上是有注释的,那就必需要去解析PO类的java文件,从里面提取出每一个PO类中:字段--》注释的对应关系来。数据库
大方向已定,咱们开搞!json
最后我这里解决这两个问题,是覆盖了三个hibernate的类的源码,大概以下:tomcat
在继续以前,先说明一下,这个确定是要修改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
知道怎么覆盖了,再说说怎么去找要覆盖哪儿,这个须要一点经验。我这里先还原成没修改时的样子,跑一下项目,发现日志有如下输出:
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个小步骤,第一步是拿到下面这样的数据:
第二步,就是像上面第一步那样,在生成create table语句时,根据table名称,取到上面这样的数据,而后再根据列名,取到注释,拼成一条下面这样的(重点是下面加粗部分):
start_time
varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '考评开始时间',
这部分纯粹考验字符串解析功力了,我说下思路,也能够直接看源码。主要是逐行读取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); } } ```
此次覆盖了org.hibernate.cfg.Configuration#generateSchemaCreationScript方法:
而后里面的内容也不用我细说了,再次根据列名查找注释,构造建表sql就好了。
这里加个成果展现:
但愿对你们有所帮助,有疑问能够直接加我。
源码在:https://github.com/cctvckl/work_util/tree/master/Hibernate_PositiveEngineer