16、Mybatis-Plus入门 - 代码生成器源码解析及生成VO案例

前言

在上篇文档中,讲解了如何生成代码及自定义模板,可以实现基础的一些代码生成。

但是除了这些代码,比如现在需要生成VO\PO等对象,以及生成其他更多功能的代码生成,应该怎么做呢。

所以接下来分析下MybatisPlus代码生成源码,这样就可以对症下药了。

源码分析

1. 获取配置项

用以下代码示例,可以看出AutoGenerator会加载各种配置。

        // 添加以上配置到AutoGenerator中
        AutoGenerator autoGenerator = new AutoGenerator(dataSourceConfig); // 数据源配置
        autoGenerator.global(globalConfig); // 全局策略配置
        autoGenerator.packageInfo(packageConfig);    // 包配置
        autoGenerator.template(templateConfig); // 配置模板
        // 生成代码,传入模板
        autoGenerator.execute(templateEngine);

2. execute执行入口

AutoGenerator的execute方法是代码生成的入口。

    public void execute(AbstractTemplateEngine templateEngine) {
   
     
        logger.debug("==========================准备生成文件...==========================");
        // 1. 加载配置
        if (null == this.config) {
   
     
            this.config = new ConfigBuilder(this.packageInfo, this.dataSource, this.strategy, this.template, this.globalConfig, this.injection);
        }
		// 2. 初始化模板引擎
        if (null == templateEngine) {
   
     
            templateEngine = new VelocityTemplateEngine();
        }

		// 3. 调用模板引擎  生成代码    
		((AbstractTemplateEngine)templateEngine).setConfigBuilder(this.config);
        ((AbstractTemplateEngine)templateEngine).init(this.config).batchOutput().open();
        logger.debug("==========================文件生成完成!!!==========================");
    }

3. 模板引擎初始化

接着调用init()方法初始化不同的模板引擎,因为这里使用的是Freemarker,所以进入FreemarkerTemplateEngine的init()方法。

    @NotNull
    public FreemarkerTemplateEngine init(@NotNull ConfigBuilder configBuilder) {
   
     
    	//  模板引擎配置
    	// 1. 设置Freemarker版本
        this.configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 2. 设置字符集
        this.configuration.setDefaultEncoding(ConstVal.UTF8);
        // 3. 设置模板加载类
        this.configuration.setClassForTemplateLoading(FreemarkerTemplateEngine.class, "/");
        return this;
    }

4. 批量输出

模板引擎初始化及配置完成后,接着进入AbstractTemplateEngine的batchOutput()方法。该方法主要是查询数据库,然后生成MVC三层代码

    @NotNull
    public AbstractTemplateEngine batchOutput() {
   
     
        try {
   
     
            ConfigBuilder config = this.getConfigBuilder();
            // 1. 查询数据库表结构信息
            List<TableInfo> tableInfoList = config.getTableInfoList();
            tableInfoList.forEach((tableInfo) -> {
   
     
                Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);
                Optional.ofNullable(config.getInjectionConfig()).ifPresent((t) -> {
   
     
                    t.beforeOutputFile(tableInfo, objectMap);
                });
                // 2. 输出相关代码
                this.outputEntity(tableInfo, objectMap);
                this.outputMapper(tableInfo, objectMap);
                this.outputService(tableInfo, objectMap);
                this.outputController(tableInfo, objectMap);
            });
            return this;
        } catch (Exception var3) {
   
     
            throw new RuntimeException("无法创建文件,请检查配置信息!", var3);
        }
    }

5. 查询表结构信息并转换Map

在batchOutput()方法,首先会查询表结构信息,并封装为TableInfo对象集合。图中可以看到TableInfo对象,会封装数据库表信息,并且会根据表信息,生成相应的controller、mappper、service等信息。
 
获取了表结构信息后,会转为Map,调用的是AbstractTemplateEngine的getObjectMap()方法。

    public Map<String, Object> getObjectMap(@NotNull ConfigBuilder config, @NotNull TableInfo tableInfo) {
   
     
        GlobalConfig globalConfig = config.getGlobalConfig();
        Map<String, Object> controllerData = config.getStrategyConfig().controller().renderData(tableInfo);
        Map<String, Object> objectMap = new HashMap(controllerData);
        Map<String, Object> mapperData = config.getStrategyConfig().mapper().renderData(tableInfo);
        objectMap.putAll(mapperData);
        Map<String, Object> serviceData = config.getStrategyConfig().service().renderData(tableInfo);
        objectMap.putAll(serviceData);
        Map<String, Object> entityData = config.getStrategyConfig().entity().renderData(tableInfo);
        objectMap.putAll(entityData);
        objectMap.put("config", config);
        objectMap.put("package", config.getPackageConfig().getPackageInfo());
        objectMap.put("author", globalConfig.getAuthor());
        objectMap.put("kotlin", globalConfig.isKotlin());
        objectMap.put("swagger", globalConfig.isSwagger());
        objectMap.put("date", globalConfig.getCommentDate());
        String schemaName = config.getDataSourceConfig().getSchemaName();
        if (StringUtils.isNotBlank(schemaName)) {
   
     
            schemaName = schemaName + ".";
            tableInfo.setConvert(true);
        } else {
   
     
            schemaName = "";
        }

        objectMap.put("schemaName", schemaName);
        objectMap.put("table", tableInfo);
        objectMap.put("entity", tableInfo.getEntityName());
        return objectMap;
    }

该方法会读取相关的策略配置,转换数据表信息为模板所需要的属性。
最后返回的Map结构入下图所示:
 

6. 输出

获取到Map之后,会输出各层代码文件。

例如输出实体类,调用的是AbstractTemplateEngine的outputEntity方法。

    protected void outputEntity(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
   
     
    	// 1. 获取实体类的名称 Base_dict
        String entityName = tableInfo.getEntityName();
        // 2. 获取输出文件夹路径  D:\output\aa\b\entity
        String entityPath = this.getPathInfo("entity_path");
        if (StringUtils.isNotBlank(entityName) && StringUtils.isNotBlank(entityPath)) {
   
     
        	// 3. 获取模板 /templates/entity.java.ftl
            this.getTemplateFilePath((template) -> {
   
     
                return template.getEntity(this.getConfigBuilder().getGlobalConfig().isKotlin());
            }).ifPresent((entity) -> {
   
     
            	// 4. 创建输出文件名 D:\output\a\b\entity\Base_dict.java
                String entityFile = String.format(entityPath + File.separator + "%s" + this.suffixJavaOrKt(), entityName);
                this.outputFile(new File(entityFile), objectMap, entity);
            });
        }

    }

最终调用的是AbstractTemplateEngine的outputFile()方法。

    protected void outputFile(@NotNull File file, @NotNull Map<String, Object> objectMap, @NotNull String templatePath) {
   
     
        if (this.isCreate(file)) {
   
     
            try {
   
     
                boolean exist = file.exists();
                if (!exist) {
   
     
                // 1. 文件是否创建成功 ,不逊在创建文件夹。D:\output\a\b\entity\Base_dict.java
                    File parentFile = file.getParentFile();
                    FileUtils.forceMkdir(parentFile);
                }
                this.writer(objectMap, templatePath, file);
            } catch (Exception var6) {
   
     
                throw new RuntimeException(var6);
            }
        }

    }

6. writer

最终调用的是模板引擎的writer方法输出文件。

    public void writer(@NotNull Map<String, Object> objectMap, @NotNull String templatePath, @NotNull File outputFile) throws Exception {
   
     
    	// 1. 根据路径获取模板
        Template template = this.configuration.getTemplate(templatePath);
        // 2. 创建输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
        Throwable var6 = null;

        try {
   
     
        	// 3. 调用模板输出
            template.process(objectMap, new OutputStreamWriter(fileOutputStream, ConstVal.UTF8));
        } catch (Throwable var15) {
   
     
            var6 = var15;
            throw var15;
        } finally {
   
     
            if (fileOutputStream != null) {
   
     
                if (var6 != null) {
   
     
                    try {
   
     
                        fileOutputStream.close();
                    } catch (Throwable var14) {
   
     
                        var6.addSuppressed(var14);
                    }
                } else {
   
     
                    fileOutputStream.close();
                }
            }

        }

    }

MybatisPlus代码生成器生成VO

1、 创建VO模板,直接套用entity.java.ftl,删除和数据库相关的标签配置;

package ${package.Entity};

<#list table.importPackages as pkg>
    import ${pkg};
</#list>
<#if swagger>
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    <#if chainModel>
        import lombok.experimental.Accessors;
    </#if>
</#if>

/**
* <p>
    * ${table.comment!}
    * </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
    @Data
    <#if superEntityClass??>
        @EqualsAndHashCode(callSuper = true)
    <#else>
        @EqualsAndHashCode(callSuper = false)
    </#if>
    <#if chainModel>
        @Accessors(chain = true)
    </#if>
</#if>
<#--<#if table.convert>
    @TableName("${schemaName}${table.name}")
</#if>-->
<#if swagger>
    @ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
<#if superEntityClass??>
    public class ${entity}VO extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
    public class ${entity}VO extends Model<${entity}> {
<#else>
    public class ${entity}VO implements Serializable {
</#if>

<#if entitySerialVersionUID>
    private static final long serialVersionUID = 1L;
</#if>
<#-- ----------  BEGIN 字段循环遍历  ---------->
<#list table.fields as field>
    <#if field.keyFlag>
        <#assign keyPropertyName="${field.propertyName}"/>
    </#if>

    <#if field.comment!?length gt 0>
        <#if swagger>
            @ApiModelProperty(value = "${field.comment}")
        <#else>
            /**
            * ${field.comment}
            */
        </#if>
    </#if>
    <#if field.keyFlag>
    <#-- 主键 -->
<#--        <#if field.keyIdentityFlag>
            @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
        <#elseif idType??>
            @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
        <#elseif field.convert>
            @TableId("${field.annotationColumnName}")
        </#if>-->
    <#-- 普通字段 -->
    <#elseif field.fill??>
    <#-- -----   存在字段填充设置   ----->
<#--        <#if field.convert>
            @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
        <#else>
            @TableField(fill = FieldFill.${field.fill})
        </#if>-->
    <#elseif field.convert>
        @TableField("${field.annotationColumnName}")
    </#if>
<#-- 乐观锁注解 -->
<#--    <#if field.versionField>
        @Version
    </#if>-->
<#-- 逻辑删除注解 -->
<#--    <#if field.logicDeleteField>
        @TableLogic
    </#if>-->
    private ${field.propertyType} ${field.propertyName};
</#list>
<#------------  END 字段循环遍历  ---------->

<#if !entityLombokModel>
    <#list table.fields as field>
        <#if field.propertyType == "boolean">
            <#assign getprefix="is"/>
        <#else>
            <#assign getprefix="get"/>
        </#if>
        public ${field.propertyType} ${getprefix}${field.capitalName}() {
        return ${field.propertyName};
        }

        <#if chainModel>
            public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
        <#else>
            public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
        </#if>
        this.${field.propertyName} = ${field.propertyName};
        <#if chainModel>
            return this;
        </#if>
        }
    </#list>
</#if>

<#if entityColumnConstant>
    <#list table.fields as field>
        public static final String ${field.name?upper_case} = "${field.name}";

    </#list>
</#if>
<#if activeRecord>
    @Override
    protected Serializable pkVal() {
    <#if keyPropertyName??>
        return this.${keyPropertyName};
    <#else>
        return null;
    </#if>
    }

</#if>
<#if !entityLombokModel>
    @Override
    public String toString() {
    return "${entity}{" +
    <#list table.fields as field>
        <#if field_index==0>
            "${field.propertyName}=" + ${field.propertyName} +
        <#else>
            ", ${field.propertyName}=" + ${field.propertyName} +
        </#if>
    </#list>
    "}";
    }
</#if>
}

1、 重写FreemarkerTemplateEngine,主要是添加生成VO逻辑;

public class MybatisFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
   
     

    @Override
    public AbstractTemplateEngine batchOutput() {
   
     
        System.out.println(" 进子类方法");
        try {
   
     
            ConfigBuilder config = this.getConfigBuilder();
            List<TableInfo> tableInfoList = config.getTableInfoList();
            tableInfoList.forEach((tableInfo) -> {
   
     
                Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);
                Optional.ofNullable(config.getInjectionConfig()).ifPresent((t) -> {
   
     
                    t.beforeOutputFile(tableInfo, objectMap);
                });
                this.outputEntity(tableInfo, objectMap);
                this.outputMapper(tableInfo, objectMap);
                this.outputService(tableInfo, objectMap);
                this.outputController(tableInfo, objectMap);
                this.outputVO(tableInfo, objectMap);
            });
            return this;
        } catch (Exception var3) {
   
     
            throw new RuntimeException("无法创建文件,请检查配置信息!", var3);
        }
    }

    /**
     * 输出VO文件
     *
     * @param tableInfo 表信息
     * @param objectMap 渲染数据
     * @since 3.0.0
     */
    protected void outputVO(TableInfo tableInfo,  Map<String, Object> objectMap) {
   
     
        // VO类名: 实体类+"VO"
        String voName = tableInfo.getEntityName()+"VO";
        // String voPath = this.getPathInfo("entity_path");
        String voPath = "D:\\output\\a\\v\\vo";
        if (StringUtils.isNotBlank(voName) && StringUtils.isNotBlank(voPath)) {
   
     
            String entityFile = String.format(voPath + File.separator + "%s" + this.suffixJavaOrKt(), voName);
            this.outputFile(new File(entityFile), objectMap, "/templates/view/mybatis/VO.java.ftl");
        }
    }

}

1、 AutoGenerator添加自定义模板引擎;

        // 设置自定义模板引擎
        AbstractTemplateEngine templateEngine = new MybatisFreemarkerTemplateEngine();
        // 生成代码,传入模板
        autoGenerator.execute(templateEngine);

1、 生成VO,这里只是演示,具体的细节需要自己修改;