Mybatis-Plus Generate 源碼分析

如果寫一個(gè)和Mybatis-Plus類似的代碼生成框架,思路比較容易想到击困。核心的幾個(gè)步驟就是:

  • 獲取數(shù)據(jù)庫表涎劈、字段信息广凸;
  • 添加相應(yīng)的規(guī)則,補(bǔ)充額外的信心蛛枚。
  • 根據(jù)表谅海、字段信息、新加的規(guī)則生成對(duì)應(yīng)的代碼蹦浦;

Mybatis-Plus整個(gè)框架依賴于Spring扭吁、Mybatis、模板引擎(freemaker或者velocity)和日志框架slf4j盲镶。

整個(gè)架構(gòu)如下:


如上所示:

  • core:是整個(gè)框架的核心侥袜。包含了對(duì)數(shù)據(jù)庫實(shí)體的反射提取,分析數(shù)據(jù)表的字段的信息溉贿,數(shù)據(jù)的CRUD
  • support:定義了相關(guān)的接口
  • generate:賦值對(duì)相關(guān)代碼的生成系馆。

本篇只針對(duì)Generate部分進(jìn)行分析。

Generate (代碼生成)

其使用方式為首先創(chuàng)建一個(gè)AutoGenerator對(duì)象顽照,此對(duì)象里面包含了所有的相關(guān)的配置信息由蘑,按照配置的不同類型組織為:

  • 總配置ConfigBuilder,會(huì)對(duì)下面的各個(gè)配置匯總代兵。
  • 注入配置InjectionConfig
  • 數(shù)據(jù)源配置DataSourceConfig
  • 數(shù)據(jù)表配置StrategyConfig
  • 包配置PackageConfig
  • 模板配置TemplateConfig
  • 全局配置GlobalConfig

使用AutoGenerator生成代碼的時(shí)候尼酿,外界設(shè)定好相關(guān)的配置類,然后賦值給AutoGenerator相關(guān)屬性植影,最后調(diào)用execute即可裳擎,非常簡(jiǎn)單。但是各種各樣的配置非常多思币,如果沒有深入的去了解鹿响,很有可能不能充分利用這個(gè)框架。

Config(配置詳解)

ConfigBuilder

ConfigBuilder會(huì)對(duì)所有的配置再一次封裝谷饿,比如對(duì)某些為null的配置設(shè)定為默認(rèn)值惶我,過參數(shù)進(jìn)行過濾、驗(yàn)證等等博投。各個(gè)配置都有了之后绸贡,調(diào)用對(duì)應(yīng)的handler執(zhí)行處理。整個(gè)處理過程一定要注意配置初始化的順序毅哗,不能打亂听怕,比如最終的表生成策略依賴于模板配置、數(shù)據(jù)源配置等等虑绵。

具體來講對(duì)應(yīng)代碼如下:

public ConfigBuilder(PackageConfig packageConfig,
                     DataSourceConfig dataSourceConfig, 
                     StrategyConfig strategyConfig,
                     TemplateConfig template, 
                     GlobalConfig globalConfig) {
        // 全局配置
        if (null == globalConfig) {
            this.globalConfig = new GlobalConfig();
        } else {
            this.globalConfig = globalConfig;
        }
        // 模板配置
        if (null == template) {
            this.template = new TemplateConfig();
        } else {
            this.template = template;
        }
        // 包配置
        if (null == packageConfig) {
            handlerPackage(this.template, this.globalConfig.getOutputDir(), new PackageConfig());
        } else {
            handlerPackage(this.template, this.globalConfig.getOutputDir(), packageConfig);
        }
        this.dataSourceConfig = dataSourceConfig;
        handlerDataSource(dataSourceConfig);
        // 策略配置
        if (null == strategyConfig) {
            this.strategyConfig = new StrategyConfig();
        } else {
            this.strategyConfig = strategyConfig;
        }
        handlerStrategy(this.strategyConfig);
}

這個(gè)類比較重要尿瞭,對(duì)于各個(gè)配置的handle也是在其進(jìn)行。比如獲取表屬性翅睛。

GlobalConfig

全局配置主要是對(duì)于整個(gè)自定生成環(huán)境的配置声搁。如目錄鸣峭,開發(fā)人員名稱,是否使用基類酥艳,文件命名等。具體來講包含有如下配置:

     /**
     * 生成文件的輸出目錄【默認(rèn) D 盤根目錄】
     */
    private String outputDir = "D://";

    /**
     * 是否覆蓋已有文件
     */
    private boolean fileOverride = false;

    /**
     * 是否打開輸出目錄
     */
    private boolean open = true;

    /**
     * 是否在xml中添加二級(jí)緩存配置
     */
    private boolean enableCache = true;

    /**
     * 開發(fā)人員
     */
    private String author;

    /**
     * 開啟 Kotlin 模式
     */
    private boolean kotlin = false;

    /**
     * 開啟 ActiveRecord 模式
     */
    private boolean activeRecord = true;

    /**
     * 開啟 BaseResultMap
     */
    private boolean baseResultMap = false;

    /**
     * 開啟 baseColumnList
     */
    private boolean baseColumnList = false;
    /**
     * 各層文件名稱方式爬骤,例如: %Action 生成 UserAction
     */
    private String mapperName;
    private String xmlName;
    private String serviceName;
    private String serviceImplName;
    private String controllerName;
    /**
     * 指定生成的主鍵的ID類型
     */
    private IdType idType;

需要注意的幾個(gè)點(diǎn):

  • 生成文件的輸出目錄因操作系統(tǒng)不同而不同充石。默認(rèn)是Windows的D盤

  • 生成的主鍵的ID類型有多種。

  •     AUTO(0, "數(shù)據(jù)庫ID自增"),
        INPUT(1, "用戶輸入ID"),
        ID_WORKER(2, "全局唯一ID"),
        UUID(3, "全局唯一ID"),
        NONE(4, "該類型為未設(shè)置主鍵類型"),
        ID_WORKER_STR(5, "字符串全局唯一ID");
    

PackageConfig

包相關(guān)的配置霞玄,這個(gè)配置比較簡(jiǎn)單骤铃。具體來講:

    /**
     * 父包名。如果為空坷剧,將下面子包名必須寫全部惰爬, 否則就只需寫子包名
     */
    private String parent = "com.baomidou";

    /**
     * 父包模塊名。
     */
    private String moduleName = null;
    
    /**
     * Entity包名
     */
    private String entity = "entity";
    
    /**
     * Service包名
     */
    private String service = "service";
    
    /**
     * Service Impl包名
     */
    private String serviceImpl = "service.impl";
    /**
     * Mapper包名
     */
    private String mapper = "mapper";
    
    /**
     * Mapper XML包名
     */
    private String xml = "mapper.xml";
    
    /**
     * Controller包名
     */
    private String controller = "web";

其實(shí)也就是對(duì)最終生成的目錄結(jié)構(gòu)的設(shè)定惫企。一個(gè)簡(jiǎn)單的例子:


TemplateConfig

模板配置類撕瞧,主要是對(duì)生成代碼文件格式的配置。我們生成不用層次的類對(duì)應(yīng)的模板是不同的狞尔,雖然可以通過字符串的方式來實(shí)現(xiàn)具體的自動(dòng)生成丛版。但是使用模板技術(shù)更加簡(jiǎn)單。對(duì)于每個(gè)層采用不同的模板:

    private String entity = ConstVal.TEMPLATE_ENTITY_JAVA;

    private String service = ConstVal.TEMPLATE_SERVICE;
    
    private String serviceImpl = ConstVal.TEMPLATE_SERVICEIMPL;
    
    private String mapper = ConstVal.TEMPLATE_MAPPER;
    
    private String xml = ConstVal.TEMPLATE_XML;
    
    private String controller = ConstVal.TEMPLATE_CONTROLLER;

這里的ConstVal里面定義全局常量偏序。

以模板文件mapper.java.vm為例:

package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};

/**
 * <p>
 * $!{table.comment} Mapper 接口
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

}
#end

其對(duì)應(yīng)生成的代碼

package com.baomidou.test.mapper;

import com.baomidou.test.entity.Permission;
import com.baomidou.mybatisplus.mapper.BaseMapper;

/**
 * <p>
 * 權(quán)限表 Mapper 接口
 * </p>
 *
 * @author Yanghu
 * @since 2018-06-08
 */
public interface PermissionMapper extends BaseMapper<Permission> {

}

DataSourceConfig

數(shù)據(jù)庫配置主要就是定義好相關(guān)數(shù)據(jù)庫页畦,用戶名,密碼等研儒,便于連接到數(shù)據(jù)庫讀到想的表字段豫缨。具體來講包含對(duì)如下信息的配置:

    /**
     * 數(shù)據(jù)庫信息查詢
     */
    private IDbQuery dbQuery;
    /**
     * 數(shù)據(jù)庫類型
     */
    private DbType dbType;
    /**
     * PostgreSQL schemaname
     */
    private String schemaname = "public";
    /**
     * 類型轉(zhuǎn)換
     */
    private ITypeConvert typeConvert;
    /**
     * 驅(qū)動(dòng)連接的URL
     */
    private String url;
    /**
     * 驅(qū)動(dòng)名稱
     */
    private String driverName;
    /**
     * 數(shù)據(jù)庫連接用戶名
     */
    private String username;
    /**
     * 數(shù)據(jù)庫連接密碼
     */
    private String password;

需要說明一下IDbQuery、ITypeConvert端朵。

  • IDbQuery是一個(gè)接口里面對(duì)查詢數(shù)據(jù)庫表好芭、字段、注釋信息的封裝冲呢。因?yàn)樾枰獫M足多種數(shù)據(jù)庫的自動(dòng)生成栓撞,所以需要正對(duì)不同數(shù)據(jù)庫實(shí)現(xiàn)IDbQuery不同的類。比如MySqlQuery就是對(duì)IDbQuery一種實(shí)現(xiàn)
   @Override
    public DbType dbType() {
        return DbType.MYSQL;
    }


    @Override
    public String tablesSql() {
        return "show table status";
    }


    @Override
    public String tableFieldsSql() {
        return "show full fields from `%s`";
    }


    @Override
    public String tableName() {
        return "NAME";
    }


    @Override
    public String tableComment() {
        return "COMMENT";
    }


    @Override
    public String fieldName() {
        return "FIELD";
    }


    @Override
    public String fieldType() {
        return "TYPE";
    }


    @Override
    public String fieldComment() {
        return "COMMENT";
    }


    @Override
    public String fieldKey() {
        return "KEY";
    }


    @Override
    public boolean isKeyIdentity(ResultSet results) throws SQLException {
        return "auto_increment".equals(results.getString("Extra"));
    }
  • ITypeConvert接口是把數(shù)據(jù)庫中filed中的類型轉(zhuǎn)為java中對(duì)應(yīng)的數(shù)據(jù)類型碗硬。具體來講MySqlTypeConvert實(shí)現(xiàn)如下:
    @Override
      public DbColumnType processTypeConvert(String fieldType) {
          String t = fieldType.toLowerCase();
          if (t.contains("char") || t.contains("text")) {
              return DbColumnType.STRING;
          } else if (t.contains("bigint")) {
              return DbColumnType.LONG;
          } else if (t.contains("int")) {
              return DbColumnType.INTEGER;
          } else if (t.contains("date") || t.contains("time") || t.contains("year")) {
              return DbColumnType.DATE;
          } else if (t.contains("text")) {
              return DbColumnType.STRING;
          } else if (t.contains("bit")) {
              return DbColumnType.BOOLEAN;
          } else if (t.contains("decimal")) {
              return DbColumnType.BIG_DECIMAL;
          } else if (t.contains("clob")) {
              return DbColumnType.CLOB;
          } else if (t.contains("blob")) {
              return DbColumnType.BLOB;
          } else if (t.contains("binary")) {
              return DbColumnType.BYTE_ARRAY;
          } else if (t.contains("float")) {
              return DbColumnType.FLOAT;
          } else if (t.contains("double")) {
              return DbColumnType.DOUBLE;
          } else if (t.contains("json") || t.contains("enum")) {
              return DbColumnType.STRING;
          }
          return DbColumnType.STRING;
      }

注意這里為什么用contains作為判斷瓤湘,因?yàn)閙ysql中可以設(shè)置數(shù)據(jù)類型具體大小。雖然大小不固定但是恩尾,其前綴是固定弛说。

  • 其次還在DataSourceConfig中創(chuàng)建了數(shù)據(jù)庫連接對(duì)象提供給外界使用。
 public Connection getConn() {
        Connection conn = null;
        try {
            Class.forName(driverName);
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

StrategyConfig

對(duì)具體生成的表字段進(jìn)行配置翰意。比如去掉哪些表前綴木人,字段前綴信柿;定義生成entity的公告字段及相關(guān)基類;排除對(duì)哪些表自動(dòng)生成醒第,或者對(duì)哪些表自動(dòng)生成渔嚷;是否根據(jù)表生成對(duì)應(yīng)的注釋。具體來講有如下設(shè)置:

/**
 * 表名稠曼、字段名形病、是否使用下劃線命名(默認(rèn) false)
 */
public static boolean DB_COLUMN_UNDERLINE = false;

/**
 * 是否大寫命名
 */
private boolean isCapitalMode = false;

/**
 * 是否跳過視圖
 */
private boolean skipView = false;

/**
 * 數(shù)據(jù)庫表映射到實(shí)體的命名策略
 */
private NamingStrategy naming = NamingStrategy.nochange;
/**
 * 數(shù)據(jù)庫表字段映射到實(shí)體的命名策略<br/>
 * 未指定按照 naming 執(zhí)行
 */
private NamingStrategy columnNaming = null;

/**
 * 表前綴
 */
private String[] tablePrefix;

/**
 * 字段前綴
 */
private String[] fieldPrefix;

/**
 * 自定義繼承的Entity類全稱,帶包名
 */
private String superEntityClass;

/**
 * 自定義基礎(chǔ)的Entity類霞幅,公共字段
 */
private String[] superEntityColumns;

/**
 * 自定義繼承的Mapper類全稱漠吻,帶包名
 */
private String superMapperClass = ConstVal.SUPERD_MAPPER_CLASS;

/**
 * 自定義繼承的Service類全稱,帶包名
 */
private String superServiceClass = ConstVal.SUPERD_SERVICE_CLASS;

/**
 * 自定義繼承的ServiceImpl類全稱司恳,帶包名
 */
private String superServiceImplClass = ConstVal.SUPERD_SERVICEIMPL_CLASS;

/**
 * 自定義繼承的Controller類全稱途乃,帶包名
 */
private String superControllerClass;

/**
 * 需要包含的表名(與exclude二選一配置)
 */
private String[] include = null;

/**
 * 需要排除的表名
 */
private String[] exclude = null;
/**
 * 【實(shí)體】是否生成字段常量(默認(rèn) false)<br>
 * -----------------------------------<br>
 * public static final String ID = "test_id";
 */
private boolean entityColumnConstant = false;

/**
 * 【實(shí)體】是否為構(gòu)建者模型(默認(rèn) false)<br>
 * -----------------------------------<br>
 * public User setName(String name) { this.name = name; return this; }
 */
private boolean entityBuilderModel = false;

/**
 * 【實(shí)體】是否為lombok模型(默認(rèn) false)<br>
 * <a >document</a>
 */
private boolean entityLombokModel = false;

/**
 * Boolean類型字段是否移除is前綴(默認(rèn) false)<br>
 * 比如 : 數(shù)據(jù)庫字段名稱 : 'is_xxx',類型為 : tinyint. 在映射實(shí)體的時(shí)候則會(huì)去掉is,在實(shí)體類中映射最終結(jié)果為 xxx
 */
private boolean entityBooleanColumnRemoveIsPrefix = false;
/**
 * 生成 <code>@RestController</code> 控制器
 * <pre>
 *      <code>@Controller</code> -> <code>@RestController</code>
 * </pre>
 */
private boolean restControllerStyle = false;
/**
 * 駝峰轉(zhuǎn)連字符
 * <pre>
 *      <code>@RequestMapping("/managerUserActionHistory")</code> -> <code>@RequestMapping("/manager-user-action-history")</code>
 * </pre>
 */
private boolean controllerMappingHyphenStyle = false;

/**
 * 是否生成實(shí)體時(shí),生成字段注解
 */
private boolean entityTableFieldAnnotationEnable = false;
/**
 * 樂觀鎖屬性名稱
 */
private String versionFieldName;

/**
 * 邏輯刪除屬性名稱
 */
private String logicDeleteFieldName;

/**
 * 表填充字段
 */
private List<TableFill> tableFillList = null;

小結(jié)

相關(guān)配置介紹完了扔傅,可以看到每一個(gè)配置對(duì)應(yīng)一個(gè)具體的層面耍共。這樣的好處在于職責(zé)清晰。從代碼層面上講也使用到了諸如觀面模式猎塞,策略模式等划提。

Handle(處理配置)

各個(gè)配置都設(shè)置好了之后就開始進(jìn)行處理了。其實(shí)就調(diào)用了一個(gè)方法而已

 public void execute() {
        logger.debug("==========================準(zhǔn)備生成文件...==========================");
        if(null == this.config) {
            this.config = new ConfigBuilder(this.packageInfo, this.dataSource, this.strategy, this.template, this.globalConfig);
            if(null != this.injectionConfig) {
                this.injectionConfig.setConfig(this.config);
            }
        }

        if(null == this.templateEngine) {
            this.templateEngine = new VelocityTemplateEngine();
        }

        this.templateEngine.init(this.pretreatmentConfigBuilder(this.config)).mkdirs().batchOutput().open();
        logger.debug("==========================文件生成完成P舷怼E敉!==========================");
    }

上面代理主要分為兩步骇塘。

  1. 第一步:根據(jù)配置信息伊履,實(shí)例化一個(gè)ConfigBuilder。它屏蔽了對(duì)配置如何處理的細(xì)節(jié)款违,初始化完成之后唐瀑,ConfigBuilder就完成了對(duì)所有配置的加載,以及對(duì)應(yīng)數(shù)據(jù)庫表插爹、字段的提取哄辣。
  2. 第二步:調(diào)用模板引擎,傳入ConfigBuilder赠尾。模板引擎根據(jù)ConfigBuilder填充對(duì)應(yīng)的模板力穗。最終生成代碼。

構(gòu)造ConfigBuilder

在構(gòu)造ConfigBuilder中气嫁,會(huì)一一相關(guān)的配置進(jìn)行handle当窗。這里主要講一下handlerStrategy,因?yàn)檫@個(gè)方法包含了對(duì)數(shù)據(jù)庫信息的提取過程寸宵,并且將數(shù)據(jù)庫表信息映射為實(shí)體崖面。

最終會(huì)走到一個(gè)名叫getTablesInfo方法元咙。里面涉及到兩個(gè)基本的、對(duì)數(shù)據(jù)表抽象的實(shí)體TableFieldTableInfo

基礎(chǔ)實(shí)體

TableField

TableField的內(nèi)容:

    /**
     * 是否需要進(jìn)行轉(zhuǎn)換
     */
    private boolean convert;
    /**
     * 是否為主鍵
     */
    private boolean keyFlag;
    /**
     * 主鍵是否為自增類型
     */
    private boolean keyIdentityFlag;
    /**
     * 對(duì)應(yīng)數(shù)據(jù)表的名稱
     */
    private String name;
    /**
     * 轉(zhuǎn)換之后的類型
     */
    private String type;
    /**
     * 轉(zhuǎn)換之后的屬性名
     */
    private String propertyName;
    /**
     * 對(duì)應(yīng)數(shù)據(jù)表的類型
     */
    private DbColumnType columnType;
    /**
     * 該字段的注釋信息
     */
    private String comment;
    /**
     * 填充信息
     */
    private String fill;
    /**
     * 自定義查詢字段列表
     */
    private Map<String, Object> customMap;

注意在設(shè)置setConvert的時(shí)候是傳入的一個(gè)StrategyConfig巫员,根據(jù)StrategyConfig的某些配置確定是否需要轉(zhuǎn)換庶香。

TableInfo

TableInfo是對(duì)數(shù)據(jù)表的抽象,具體來講:

    /**
     * 是否轉(zhuǎn)換
     */     
    private boolean convert;
    /**
     * 表名
     */
    private String name;
    /**
     * 表注釋
     */
    private String comment;
    /**
     * 表所對(duì)應(yīng)的實(shí)體名
     */
    private String entityName;
    /**
     * 表所對(duì)應(yīng)的mapper名
     */
    private String mapperName;
    /**
     * 表所對(duì)應(yīng)的xml名
     */
    private String xmlName;
    /**
     * 表所對(duì)應(yīng)的service名
     */
    private String serviceName;
    /**
     * 表所對(duì)應(yīng)的serviceimpl名
     */
    private String serviceImplName;
    /**
     * 表所對(duì)應(yīng)的controller名
     */
    private String controllerName;
    /**
     * 表所包含的所有field集合
     */
    private List<TableField> fields;
    /**
     * 公共字段
     */
    private List<TableField> commonFields;
    /**
     * 所依賴的包名
     */
    private List<String> importPackages = new ArrayList<>();
    /**
     * 說有字段連在一起的字符串简识,用于日志信息
     */
    private String fieldNames;

這需要注意一點(diǎn)的就是在設(shè)置fields的時(shí)候需要根據(jù)fields的數(shù)據(jù)類型引入相應(yīng)的包名赶掖。做法就是在設(shè)置fields的時(shí)候根據(jù)field類型引入。具體來講:

public void setFields(List<TableField> fields) {
        if (CollectionUtils.isNotEmpty(fields)) {
            this.fields = fields;
            // 收集導(dǎo)入包信息财异,注意為什么用HashSet。因?yàn)镠ashSetk可以自動(dòng)去除重復(fù)的key
            Set<String> pkgSet = new HashSet<>();
            for (TableField field : fields) {
                if (null != field.getColumnType() && null != field.getColumnType().getPkg()) {
                    pkgSet.add(field.getColumnType().getPkg());
                }
                if (field.isKeyFlag()) {
                    // 主鍵
                    if (field.isConvert() || field.isKeyIdentityFlag()) {
                        pkgSet.add("com.baomidou.mybatisplus.annotations.TableId");
                    }
                    // 自增
                    if (field.isKeyIdentityFlag()) {
                        pkgSet.add("com.baomidou.mybatisplus.enums.IdType");
                    }
                } else if (field.isConvert()) {
                    // 普通字段
                    pkgSet.add("com.baomidou.mybatisplus.annotations.TableField");
                }
                if (null != field.getFill()) {
                    // 填充字段
                    pkgSet.add("com.baomidou.mybatisplus.annotations.TableField");
                    pkgSet.add("com.baomidou.mybatisplus.enums.FieldFill");
                }
            }
            if (!pkgSet.isEmpty()) {
                this.importPackages = new ArrayList<>(Arrays.asList(pkgSet.toArray(new String[]{})));
            }
        }
    }

getTablesInfo

先介紹一下其中的處理邏輯:

  1. 判斷是否設(shè)置了同時(shí)設(shè)置了include和exclude

  2. 保存所有表的信息唱遭,包含排除的表戳寸,需要生成的表。

  3. 根據(jù)sql查詢表的信息拷泽,然后依次將數(shù)據(jù)映射到對(duì)應(yīng)的基礎(chǔ)實(shí)體上(TableInfo疫鹊,F(xiàn)iledInfo)。

    1. 如果設(shè)置了include或者exclude司致,再進(jìn)一步刪選拆吆。比如過濾用戶輸入不存在的表。
    2. 最終得到includeTableList表脂矫,禮包包含了需要轉(zhuǎn)換的表名稱
    3. 調(diào)用convertTableFields將表中的filed轉(zhuǎn)為基礎(chǔ)實(shí)體枣耀。
    4. 最后調(diào)用processTable將表、字段信息庭再、其他信息填充到TableInfo中捞奕。完成TableInfo基礎(chǔ)實(shí)體的構(gòu)造。

其中用到的幾條sql語句如下:

  • show table status:獲取表信息拄轻,比如表名颅围、創(chuàng)建(修改)時(shí)間、表注釋恨搓,數(shù)量條數(shù)等
  • show full fields from xxx:從特定表中獲取表所有字段的信息院促,比如字段名、字段類型斧抱,字段注釋以及該表的主鍵等常拓。

其中使用的而是JDBC最為簡(jiǎn)單的讀取數(shù)據(jù)庫。代碼簡(jiǎn)化一下

//映射Tables
preparedStatement = connection.prepareStatement(tablesSql);
ResultSet results = preparedStatement.executeQuery();
TableInfo tableInfo;
while (results.next()) {   
    ......
    includeTableList.add(tableInfo);
   
}

//映射Fields
for (TableInfo ti : includeTableList) {
           PreparedStatement preparedStatement = connection.prepareStatement(tableFieldsSql);
            ResultSet results = preparedStatement.executeQuery();
           TableField field = new TableField();
            while (results.next()) {
            ......
                 fieldList.add(field);
            }
}

tableInfo.setFields(fieldList);
tableInfo.setCommonFields(commonFieldList);
}

至此所有表以及所有表對(duì)應(yīng)的字段已經(jīng)完全映射到了基礎(chǔ)實(shí)體辉浦。接下來就是根據(jù)基礎(chǔ)實(shí)體的內(nèi)容墩邀,填充對(duì)應(yīng)的模板。

Template(模板生成)

有了上面所產(chǎn)生的實(shí)體盏浙,下面就是填模板的過程了眉睹。入口如下:

if(null == this.templateEngine) {
            this.templateEngine = new VelocityTemplateEngine();
        }

        this.templateEngine.init(this.pretreatmentConfigBuilder(this.config)).mkdirs().batchOutput().open();
       

TemplateEngine(模板引擎)

MP現(xiàn)在支持兩種模板引擎Velocity荔茬、Freemarker。這里以Velocity為例竹海。

模板生成相關(guān)一共有三個(gè)類慕蔚,分別是AbstractTemplateEngine、FreemarkerTemplateEngine斋配、VelocityTemplateEngine孔飒。AbstractTemplateEngine是抽象類定義了相關(guān)的接口。具體來講艰争,提供了如下信息:

其中的writertemplateFilePath為抽象方法坏瞄,根據(jù)不同的模板引擎,選擇不同的實(shí)現(xiàn)甩卓。最終是調(diào)用batchOutput來輸出所有自動(dòng)生成的代碼鸠匀。

在batchOutPut中將各個(gè)層級(jí)的對(duì)象,根據(jù)模板路徑逾柿,生成最終的文件缀棍。

//遍歷所有的表信息,生成文件
List<TableInfo> tableInfoList = this.getConfigBuilder().getTableInfoList();
            for (TableInfo tableInfo : tableInfoList) {
                Map<String, Object> objectMap = this.getObjectMap(tableInfo);
                Map<String, String> pathInfo = this.getConfigBuilder().getPathInfo();
                TemplateConfig template = this.getConfigBuilder().getTemplate();
                
                // Mp.java
                String entityName = tableInfo.getEntityName();
                if (null != entityName && null != pathInfo.get(ConstVal.ENTITY_PATH)) {
                    String entityFile = String.format((pathInfo.get(ConstVal.ENTITY_PATH) + File.separator + "%s" + this.suffixJavaOrKt()), entityName);
                    if (this.isCreate(entityFile)) {
                        this.writer(objectMap, this.templateFilePath(template.getEntity(this.getConfigBuilder().getGlobalConfig().isKotlin())), entityFile);
                    }
                }
                ......
                
                // MpMapper.xml
                // IMpService.java
                // MpServiceImpl.java
                // MpController.java
                }
            }
}
            

接下來看一下VelocityTemplateEngine中的write方法机错。首先會(huì)進(jìn)行初始化配置

 public VelocityTemplateEngine init(ConfigBuilder configBuilder) {
        //將configBuilder傳給父類爬范,在父類中需要用到
        super.init(configBuilder);
        if (null == velocityEngine) {
            Properties p = new Properties();
            p.setProperty(ConstVal.VM_LOADPATH_KEY, ConstVal.VM_LOADPATH_VALUE);
            p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, "");
            p.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);
            p.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);
            p.setProperty("file.resource.loader.unicode", "true");
            //初始化模板引擎
            velocityEngine = new VelocityEngine(p);
        }
        return this;
    }

父類中調(diào)用writer,并將objectMap(包含所有的映射信息)傳入,根據(jù)templatePath(不同類型模板不一樣)創(chuàng)建template弱匪。最后將模板內(nèi)容依據(jù)objectMap替換掉青瀑。其中的模板路徑則根據(jù)之前的TemplateConfig得到

    @Override
    public void writer(Map<String, Object> objectMap, String templatePath, String outputFile) throws Exception {
        if (StringUtils.isEmpty(templatePath)) {
            return;
        }
        Template template = velocityEngine.getTemplate(templatePath, ConstVal.UTF8);
        FileOutputStream fos = new FileOutputStream(outputFile);
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, ConstVal.UTF8));
        template.merge(new VelocityContext(objectMap), writer);
        writer.close();
        logger.debug("模板:" + templatePath + ";  文件:" + outputFile);
    }

這里以生存entity為例。

生成的entity

package com.baomidou.test.entity;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;

/**
 * <p>
 * 權(quán)限表
 * </p>
 *
 * @author wesly
 * @since 2018-06-08
 */
public class Permission extends Model<Permission> {

    private static final long serialVersionUID = 1L;

    /**
     * 主鍵
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 上級(jí)ID
     */
    private Long pid;
       
    ......
       
    private String description;

    @Override
    public String toString() {
        return "Permission{" +
        ", id=" + id +
        ", pid=" + pid +
        ", title=" + title +
        ", type=" + type +
        ", state=" + state +
        ", sort=" + sort +
        ", url=" + url +
        ", permCode=" + permCode +
        ", icon=" + icon +
        ", description=" + description +
        "}";
    }
}

總結(jié)

Generate部分總體來講思路比價(jià)簡(jiǎn)單萧诫。麻煩的部分在于如何去對(duì)各個(gè)部拆分狱窘。最后簡(jiǎn)單畫出了怎個(gè)生成框架的類圖結(jié)構(gòu)如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市财搁,隨后出現(xiàn)的幾起案子蘸炸,更是在濱河造成了極大的恐慌,老刑警劉巖尖奔,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搭儒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡提茁,警方通過查閱死者的電腦和手機(jī)淹禾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茴扁,“玉大人铃岔,你說我怎么就攤上這事。” “怎么了毁习?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵智嚷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我纺且,道長(zhǎng)盏道,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任载碌,我火速辦了婚禮猜嘱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫁艇。我一直安慰自己朗伶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布步咪。 她就那樣靜靜地躺著论皆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歧斟。 梳的紋絲不亂的頭發(fā)上纯丸,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天偏形,我揣著相機(jī)與錄音静袖,去河邊找鬼。 笑死俊扭,一個(gè)胖子當(dāng)著我的面吹牛队橙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萨惑,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捐康,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了庸蔼?” 一聲冷哼從身側(cè)響起解总,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姐仅,沒想到半個(gè)月后花枫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掏膏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年劳翰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馒疹。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佳簸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颖变,到底是詐尸還是另有隱情生均,我是刑警寧澤听想,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站疯特,受9級(jí)特大地震影響哗魂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漓雅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一录别、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邻吞,春花似錦组题、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旺遮,卻和暖如春赵讯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耿眉。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工边翼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸣剪。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓组底,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親筐骇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子债鸡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)铛纬,斷路器厌均,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,982評(píng)論 3 119
  • 時(shí)間過得這樣快,櫻花散盡悔详,薔薇盛開镊屎,梔子謝幕,初荷綻放茄螃,轉(zhuǎn)眼缝驳,我們的人生就這樣疾徐不定的,一路走遠(yuǎn)了。 有好多事我...
    衿悠閱讀 432評(píng)論 0 0
  • 文/杠杠 她用狱,名喚水墨畫运怖,宰相之女,她的父親輔助過先皇夏伊,如今皇上也讓他三分嚣州⊥校可她休涤,五歲那年得了罕見的大病敏弃,她父親找...
    啊哈哈哈在笑什么閱讀 285評(píng)論 0 1
  • 周五晚上從同事的輔導(dǎo)教室出來,已到10點(diǎn)鲁森,沒想女兒還進(jìn)學(xué)校跑了一圈步祟滴,雖說跑的不多,但已有鍛煉的意識(shí)歌溉,值得...
    靜等花開之心路閱讀 265評(píng)論 6 11