懶要懶到底,能自動的就不要手動,Hibernate正向工程完成Oracle數據庫到MySql數據庫轉換(含字段轉換、註釋)

需求描述

需求是這樣的:由於咱們目前的一個老項目是Oracle數據庫的,這個庫呢,數據庫是沒有註釋的,並且字段名和表名都是大寫風格,好比java

在代碼層面的po呢,之前也是沒有任何註釋的,可是通過這些年,你們慢慢踩坑多了,也給po加上了一些註釋了,好比:mysql

現狀就是這樣,再說說目標是:但願把這個庫能轉成mysql,表名和字段名最好都用下劃線分隔每一個單詞,字段呢,最好能有註釋。也就是差很少下面這樣:git

方案分析

最先我嘗試的就是hibernate正向工程,建一個空的mysql庫,而後配置hibernate的選項爲:github

這樣的話呢,就會自動在咱們指定的mysql數據庫生成表了,不過,有兩個瑕疵是:spring

  1. 生成的表,字段和表名都是和PO裏同樣的駝峯格式;
  2. 沒有註釋。

第一個問題,我這邊是經過覆蓋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

問題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裏。

大體流程就是這樣的,代碼以下:

展開查看
```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方法:

而後裏面的內容也不用我細說了,再次根據列名查找註釋,構造建表sql就好了。

這裏加個成果展現:

總結

但願對你們有所幫助,有疑問能夠直接加我。

源碼在:https://github.com/cctvckl/work_util/tree/master/Hibernate_PositiveEngineer

相關文章
相關標籤/搜索