• 懒要懒到底,能自动的就不要手动,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

  • 相关阅读:
    NYOJ-开灯问题
    cocos2dx 3.0 飞机大战
    Java 实现享元(Flyweight)模式
    MongoDB 操作手冊CRUD查询指针
    均值滤波
    cxf调用c#的webservice
    SharePoint 2013 术语和术语集介绍
    Unity3d 网络编程(二)(Unity3d内建网络各项參数介绍)
    linux服务器在运行210天左右宕机
    好的用户界面-界面设计的一些技巧
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/11726237.html
Copyright © 2020-2023  润新知