MyBatis Generator (MBG),修改源碼以適應 MySQL 大小寫敏感配置的各種情況、適應分表時動態替換表名

本文對應的項目

本文實現方式是修改 MyBatis Generator 源碼,最新的實現方式是通過 MyBatis Generator 擴展來實現,點擊前往

通過本項目,可以學到的知識點

  1. 可以理解使用MBG的大致流程。
  2. 本文中用到的MBG配置可以作爲一個標準配置的參考。
  3. spring boot 2.1.1 獲取 application.yml 配置信息,項目【create-table-property】是一個很好的參考。
  4. 通過MBG如何生成dal與model項目。
    • 生成的代碼,絕大部分可直接使用,比如簡單的增、刪、改、查。
    • 對應數據庫表的實體類,一張表一個實體類,可用於在各層傳遞基於表數據的業務數據。
  5. 通過 IDEA 管理多項目。
    • 獲取項目源碼,用 IDEA 導入的時候,指向根目錄的 pom.xml 即可。

先了解一下 lower_case_table_names 參數

官方文檔:Identifier Case Sensitivity

  1. lower_case_table_names是mysql一個大小寫敏感設置的屬性,此參數不可以動態修改,必須重啓數據庫。
  2. unix,linux下lower_case_table_names默認值爲 0 .Windows下默認值是 1 .Mac OS X下默認值是 2。

參數說明

  • lower_case_table_names=0 表名存儲爲給定的大小寫。比較時:區分大小寫。大小寫敏感(Unix,Linux默認)。

創建的庫表將原樣保存在磁盤上。如create database TeSt;將會創建一個TeSt的目錄,create table AbCCC …將會原樣生成AbCCC.frm。SQL語句也會原樣解析。

  • lower_case_table_names=1 表名存儲爲小寫。比較時:不區分大小寫。大小寫不敏感(Windows默認)。

創建的庫表時,MySQL將所有的庫表名轉換成小寫存儲在磁盤上。SQL語句同樣會將庫表名轉換成小寫。如需要查詢以前創建的Test_table(生成Test_table.frm文件),即便執行select * from Test_table,也會被轉換成select * from test_table,致使報錯表不存在。

  • lower_case_table_names=2 表名存儲爲給定的大小寫。比較時:小寫。

創建的庫表將原樣保存在磁盤上。但SQL語句將庫表名轉換成小寫。

不適用場景

如果開發環境、生產環境均配置成1或者2,則本文中有關大小寫敏感的修改都是無意義的。

但是如果分庫時依賴表名替換,則又是適用的,見以下【適用場景】中的場景二。

適用場景

最終目標:MBG 生成的xml文件中的sql腳本的表名,保持與對應表名在建表時的大小寫一致,保持大小寫敏感(表名可在MBG需要的配置文件中配置,以該配置爲準)。這樣可以適應以上lower_case_table_names的三種配置值。

爲了達到以上目標,運行生成表配置內容的項目,一定要連接參數lower_case_table_names配置爲0或者2的數據庫服務器,並且是配置爲0或者2之後才創建的數據表,否則,生成的表配置內容的表名,是以全部小寫爲基準的,並非駝峯式命名法。表配置內容生成好之後,重新生成 mapper 時連接的數據庫服務器的lower_case_table_names配置值,對生成結果沒有影響。

場景一

  1. 其中有數據庫服務器被設置成大小寫不敏感(比如阿里雲的雲數據庫,截至目前2018年12月9號,還不支持配置成大小寫敏感),即 lower_case_table_names=1,且該參數不能修改。
  2. 爲了統一命名規範使用駝峯式命名法,包括:數據庫名、數據庫表名、數據庫字段名、編程語言。這樣的話,可以控制lower_case_table_names的linux服務器,就可以將該參數設置爲0,即大小寫敏感。
  3. 用 MGB 生成的 Mapper 類名,以及 xml 文件中的表名,需要與創建表名時的原始大小寫一致,以適應在lower_case_table_names=0(linux)或者lower_case_table_names=2(windows)的情況。

當然,讀到這裏,你可能會覺得奇怪,數據庫的庫名、表名、字段名,業界都是用下劃線分開的,其餘字母全是小寫,所以,該參數被配置成什麼都不需要關心。

如果你也這麼認爲,那麼,本文對你是沒有價值的。

本文要解決的問題是,數據庫的庫名、表名、字段名,需要保持跟 Java 的編碼規範一致的場景。如果都統一成一種編碼規則,比如統一用駝峯式命名法,那麼,不用在兩種編碼習慣上切換,可以提高編碼效率和減少不必要的麻煩,且繼續往下看。

場景二

  • 分表,利用MyBatis插件,根據業務規則,對錶名進行動態替換。
  • erpTrade表分成了120個表,那麼在某一次業務操作中,需要將erpTrade替換成erpTrade_xyz,其中xyz爲從001120的其中一個數字,則需要將MBG生成的xml裏sql腳本中的表名用 `(左上角數字鍵1左邊、Tab鍵上邊、Esc鍵下邊的鍵)引起來。

將會分享我的基於 MyBatis 插件分庫分表項目。

需求場景

  1. 首先,我項目的 Java 代碼規範是變量命名應用駝峯式命名法(Camel-Case)。數據庫表名及字段名,則用下劃線命名法(即用下劃線分隔不同單詞)。
  2. 我用 MBG 生成的代碼,通過配置可以將下劃線去掉,同時將下劃線後的第一個字母轉爲大寫,這樣是符合駝峯式命名法的。
  3. 但是,問題來了。我們項目前後端分離,前端調用 Restful Api,在傳遞的參數中難免需要以表名來定義對象,而以字段名作爲對象的屬性來傳遞參數,而 Java 寫的 Api 在接收參數時,是用 Pojo 來跟前端傳的參數匹配的。
  4. 這樣有兩個問題,前端在傳參數的時候需要將表名和字段名由下劃線命名法轉爲駝峯式命名法,Java 代碼也以同樣的方式定義對應的類名以及屬性字段,在這個過程中,容易出錯,相對直接 copy 表名及字段名,需要做額外的工作,而且前後端都有。
  5. 如果數據庫表名和字段名本身就是駝峯式命名法的話,寫代碼的時候直接 copy 表名或字段名,這樣既不容易出錯,還能節省時間。說幹就幹,改!表名及字段名遵循駝峯式命名法。
  6. 這樣做的目的無非是讓大家能偷偷懶,還減少出錯的概率,同時也輕鬆的達到了統一代碼規範的目的。
  7. 在windows環境下,如果遇到lower_case_table_names=1或者該參數未配置(未配置的時候默認爲1),運行MBG,生成的sql腳本,全是小寫,需要花費額外時間來解決環境問題。
  8. 爲了一勞永逸,有了本文和對應的項目。
  9. 隨着業務發展,到了需要分庫分表的時候,本文所解決的問題,更是不可或缺。

解決辦法

  1. 怎麼破?查了很多資料,也看了官方文檔,都沒有找到相應的配置,也就是說,保持數據庫表名大小寫來生成對應的類和 mapper 文件,是不能通過配置來解決的。
  2. 只能看源碼了。思路是:一步步跟蹤,找到了真正取數據庫表名的地方。
  3. 雖然過程比較痛苦,但是達到目的之後,還是小有成就感的。
  4. 其實改動很少,只是找到要改的地方纔是關鍵。就象平時改 Bug 一樣,修改可能很小,但是找到要修改的地方纔是最花時間和精力的,每每這個時候,就非常渴求得力的測試人才。

廢話少說,下面切入正題。

關於 MBG (MyBatis Generator)

筆者寫這篇文章的時候,Release 最新版本是1.3.5(2016年九月7號發佈),該版本已經足夠滿足本文提到的需求了,作者主導的項目均採用該版本。

  1. 下載地址:MyBatis Generator Release 1.3.5
  2. 官方文檔

關於 MBG 的介紹,看官網文檔足夠了,本文的重點是修改源代碼,讓 MBG 在生成的代碼裏,對應的類和 mapper 文件保持與對應的表名大小寫不變。

備註,實現方式已經改爲通過擴展來解決問題了,不過,如果需要研究一下源碼,還是有必要往下讀的。

作爲研究,作者去mybatis/generator 官方 github 下載了最新版 V1.3.7(2018年7月5號發佈),用最新版重新操作了一遍。

下載的源碼文件是generator-mybatis-generator-1.3.7.zip,解壓之後,找到目錄 generator-mybatis-generator-1.3.7\core\mybatis-generator-core,這個目錄就是我們需要的源碼目錄。該目錄下,只保留 src 目錄和 pom.xml,其餘刪除。

用 IDEA 導入項目,如果依賴下載很慢,建議添加阿里雲倉庫,在 pom.xml,倒數第二行</scm>後面、最後一行</project>前面,插入以下內容:

  <repositories>
    <repository>
      <id>maven-ali</id>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
        <updatePolicy>always</updatePolicy>
        <checksumPolicy>fail</checksumPolicy>
      </snapshots>
    </repository>
  </repositories>

修改源碼

才下操作,均針對最新版 v1.3.7。

一共需要修改 org.mybatis.generator.api.IntrospectedTable 這個類的兩個方法。

    /**
     * Gets the fully qualified table name at runtime.
     *
     * @return the fully qualified table name at runtime
     */
    public String getFullyQualifiedTableNameAtRuntime() {
//        return internalAttributes
//                .get(InternalAttribute.ATTR_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME);
        return this.getTableConfiguration().getTableName();
    }

    /**
     * Gets the aliased fully qualified table name at runtime.
     *
     * @return the aliased fully qualified table name at runtime
     */
    public String getAliasedFullyQualifiedTableNameAtRuntime() {
//        return internalAttributes
//                .get(InternalAttribute.ATTR_ALIASED_FULLY_QUALIFIED_TABLE_NAME_AT_RUNTIME);
        return this.getTableConfiguration().getTableName();
    }

這兩個方法裏面的前兩行註釋了的代碼是官方的,最後那行代碼是修改之後的。

生成對應的類和 mapper 文件

對應的測試數據庫腳本

create schema if not exists mbg default character set utf8;

use mbg;
set names utf8;

drop table if exists erpTrade;
create table erpTrade
(
   tradeID              int not null auto_increment,
   tid                  bigint comment '訂單號',
   shopID               int,
   memberID             bigint comment '針對的會員ID,同步會員時反寫',
   buyerNick            national varchar(50),
   sellerID             bigint comment '店鋪用戶,主旺旺 ID',
   primary key (tradeID)
);

drop table if exists erpEnterpriseMember;
create table erpEnterpriseMember
(
   memberID             bigint not null auto_increment,
   shopID               int,
   nickname             varchar(50) comment '會員暱稱',
   realName             varchar(50) comment '真實姓名',
   sex                  int comment '性別 1.男 2.女 3.未知',
   created              int,
   modified             int,
   primary key (memberID)
);

drop table if exists erpShopConfig;
create table erpShopConfig
(
   ID                   int not null auto_increment,
   shopID               int,
   mobile               varchar(200) comment '接收短信手機號,多個以逗號隔開',
   email                varchar(200) comment '郵箱地址,多個以逗號隔開',
   isNotifyBalanceLess  int comment '當短信賬戶餘額爲X條時,系統免費發送短信提醒您及時充值。',
   balanceLessValue     int comment '餘額不足提醒值',
   isNotifyBeforeExpire int comment '是否發送軟件快到期提醒',
   beforeDaysExpire     int comment '軟件快到期提醒時間,提前天數',
   created              int,
   modified             int,
   primary key (ID)
);

生成表配置信息的 Java 工具類

MBG 基於一個 xml 配置文件,在這個配置文件裏,有跟表相關的配置,爲了達到我的需求,需要一張表對應一行配置信息,所以,我寫了一個類來自動生成,這樣,在增減表,或者別的項目裏面,可以簡單的運行這個類來生成,減少手工勞動。

更詳細的,請看:https://github.com/uncleAndyChen/mybatis-generator/tree/master/create-table-property

MBG需要的配置文件比較全面的,在工作中實際用到的文件內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--數據庫驅動-->
    <classPathEntry location="mysql-connector-java-5.1.31.jar"/>
    <!--<context id="DB2Tables" targetRuntime="MyBatis3">-->
    <!--如果你希望不生成和Example查詢有關的內容,那麼可以按照如下進行配置:-->
    <!--<context id="DB2Tables" targetRuntime="MyBatis3Impl">-->
    <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
    <!--<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--數據庫鏈接地址賬號密碼-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://192.168.0.130:3306/mbg?useUnivalue=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;failOverReadOnly=false"
                        userId="root" password="root">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成Model類存放位置-->
        <javaModelGenerator targetPackage="mybatis.generator.model.entity" targetProject="mybatis.generator.model/src/main/java/">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成映射文件存放位置-->
        <sqlMapGenerator targetPackage="mappers\original" targetProject="mybatis.generator.dal\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成Dao類存放位置
        當type=XMLMAPPER時,會生成一個XXX.xml文件內有各種sql語句,是mapper的實現。
        當type=ANNOTATEDMAPPER時,會直接在mapper接口上添加註釋。
        -->
        <!--
        http://blog.csdn.net/qq_27376871/article/details/51360638
        MyBatis3:
        ANNOTATEDMAPPER:基於註解的Mapper接口,不會有對應的XML映射文件
        MIXEDMAPPER:XML和註解的混合形式,(上面這種情況中的)SqlProvider註解方法會被XML替代。
        XMLMAPPER:所有的方法都在XML中,接口調用依賴XML文件。
        MyBatis3Simple:
        ANNOTATEDMAPPER:基於註解的Mapper接口,不會有對應的XML映射文件
        XMLMAPPER:所有的方法都在XML中,接口調用依賴XML文件。
        -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.generator.dal.mapper"
                             targetProject="mybatis.generator.dal/src/main/java/">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--生成對應表及類名-->
        <table tableName="erpEnterpriseMember" domainObjectName="EnterpriseMember"><property name="useActualColumnNames" value="true"/><generatedKey identity="true" type="post" column="memberID" sqlStatement="Mysql"/></table>
        <table tableName="erpShopConfig" domainObjectName="ShopConfig"><property name="useActualColumnNames" value="true"/><generatedKey identity="true" type="post" column="ID" sqlStatement="Mysql"/></table>
        <table tableName="erpTrade" domainObjectName="erpTrade"><property name="useActualColumnNames" value="true"/><generatedKey identity="true" type="post" column="tradeID" sqlStatement="Mysql"/></table>
    </context>
</generatorConfiguration>

運行 MBG

  1. IntelliJ IDEA 爲例
    爲了測試,除了從官網下載的 MBG 源碼外,另外添加了兩個模塊。
    先創建一個空項目,然後導入 MBG,添加兩個測試模塊,或者直接從碼雲下載我的代碼。注意模塊的包路徑要與配置文件裏的一致,否則會提示路徑找不到。
    創建一個空項目

    導入模塊

直接運行源碼

新建一個啓動應用,選 MBG 下的 ShellRunner 作爲啓動入口

按下圖所示,在參數一欄寫:-configfile generatorConfig.xml -overwrite,選 mybatis-generator-1.3.5 模塊

接下來,應該知道如何運行或者調試了吧。

生成 jar 包,一次生成,隨處運行

  1. 將 MBG 打包成 jar 包,把該 jar 包和 mysql-connector-java-5.1.31.jar、generatorConfig.xml 一起放到項目的根目錄下,在 dos 窗口,或者 IDEA 的 Terminal 窗口直接運行下面的命令。
java -Dfile.encoding=UTF-8 -cp mybatis-generator-1.3.5.jar org.mybatis.generator.api.ShellRunner -configfile generatorConfig.xml -overwrite
  1. 推薦用這種方式。這樣不需要將 MBG 項目添加到公司的具體項目,而且通常這項工作由某一個人做就行了,其它項目組成員,可以不知道該工具的存在。

下面是生成 MBG jar 包的步驟

按 Ctrl+Alt+Shift+s,進入項目設置界面

生成 MBG jar 包第一步,配置

生成 MBG jar 包第二步,Build Artifacts…

注意事項

  1. 當表結構發生變化時,需要重新運行 MBG 生成新的代碼,所以,生成的代碼,不能有修改行爲,否則下次重新生成後,改過的代碼會被覆蓋。
  2. 重新生成時,*Mapper.xml 文件會被改寫錯,MBG 沒有重新生成該文件,而是改寫,這種改寫有問題,還沒來得及深究。
  3. 針對第2點的問題,我的解決辦法是,重新生成前,將對應的 *Mapper.xml 刪掉。如果只生成一張表的代碼,則只刪除對應的 mapper 文件即可,Pojo 文件重新生成後是正確的,不用管。
  4. 也可以用下面的腳本簡單粗暴的刪除全部 xml 文件。反正會重新生成,如果文件內容跟原來一致,不會產生新的 svn/git 提交。需要注意的是,下面的腳本我是在 idea 的 Terminal 窗口執行的,這裏的命令語法請保持 linux 風格。這樣更省事兒,推薦這種方案,這樣不用每次變更某一張表的時候找到對應的表配置,也不用單獨去刪除對應的 mapper.xml 文件,只要把所有表的配置都操持有效(即不要註釋掉)就行,省時省力!
# 注意:*Mapper.xml 文件,每次重新生成都需要先刪除,否則部分內容會重複生成,導致錯誤
del/f/s/q C:\workspace\mbg\mybatis-generator\demo-domain-dal\src\main\java\demo\domain\dal\mapper\original\*.*
del/f/s/q C:\workspace\mbg\mybatis-generator\demo-domain-model\src\main\java\demo\domain\model\entity\*.*

del/f/s/q C:\workspace\mbg\mybatis-generator\demo-domain-dal\src\main\resources\mappers\original\*.xml
java -Dfile.encoding=UTF-8 -cp mybatis-generator-1.3.7.jar;mybatis-generator-enhance.jar org.mybatis.generator.api.ShellRunner -configfile generatorConfig.xml -overwrite

關於作者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章