zoukankan      html  css  js  c++  java
  • 懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)

    需求描述

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

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

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

    方案分析

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

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

    1. 生成的表,字段和表名都是和PO里一样的驼峰格式;
    2. 没有注释。

    第一个问题,我这边是通过覆盖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

  • 相关阅读:
    基于element-ui图片封装组件
    计算时间间隔具体每一天
    C语言学习笔记 —— 函数作为参数
    AtCoder Beginner Contest 049 题解
    AtCoder Beginner Contest 048 题解
    AtCoder Beginner Contest 047 题解
    AtCoder Beginner Contest 046 题解
    AtCoder Beginner Contest 045 题解
    AtCoder Beginner Contest 044 题解
    AtCoder Beginner Contest 043 题解
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11726237.html
Copyright © 2011-2022 走看看