使用 mybatis-plus-generator 快速生成代码 (velocity模板引擎)

一、简介

使用 velocity 模板引擎 + mybatis-plus-generator 快速生成代码

(一)功能介绍

自动生成 controller、service、mapper、entity、dto、bo

根据数据库连接信息灵活生成基于spring boot 的代码, 扩展性高, 可以适配多模块项目, 也可以脱离spring boot运行

目前支持功能:

  1. 可以根据模板引擎自定义代码生成样式
  2. 自定义代码存放目录
  3. 支持跨模块生成
  4. 支持多种数据库

(二)设计技术栈

  1. spring boot
  2. mybatis-plus-generator
  3. velocity 模板引擎

二、实现逻辑

(一)引入依赖

<!--datasource-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!--代码生成-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
</dependency>

(二)配置数据DTO封装

@Builder
@Data
public class ModelGeneratorDTO {

    /**
     * 需要生成的表名数组
     */
    private String[] tableNameArr;

    /**
     * 项目绝对路径+微服务模块名称+项目编译源目录 (通过 String projectPath = Paths.get(System.getProperty("user.dir")).toAbsolutePath().toString(); 获取)
     */
    private String outputPath;

    /**
     * 微服务模块名称 (generator-model)
     */
    private String modelName;

    /**
     * 项目编译目录 (src/main/java)
     */
    private String compilePath;

    /**
     * 父包位置
     */
    private String parentPackage;


    /**
     * entity 包位置, 会自动拼接上父包的位置
     */
    private String entityPackage;
    /**
     * 自定义dto和bo的包名
     */
    private String dtoPackage;
    private String boPackage;
    /**
     * mapper 位置
     */
    private String mapperPackage;
    /**
     * mapper xml 文件位置,如:resource/mapper
     */
    private String mapperXmlPath;
    /**
     * service 包位置
     */
    private String servicePackage;
    /**
     * serviceImpl 包位置, 如service: service, serviceImpl: service.impl, impl实现类会自动添加到对应目录
     */
    private String serviceImplPackage;
    /**
     * controller 包位置
     */
    private String controllerPackage;
}

(三)编写代码生成handler

  1. 数据源
// 1. 代码生成器的数据库配置
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(dataSource)
        .dbQuery(new MySqlQuery())  //
        .typeConvert(new MySqlTypeConvert())    // 类型转换配置
        .keyWordsHandler(new MySqlKeyWordsHandler())    // 关键字配置
        .databaseQueryClass(SQLQuery.class) // 设置查询实例
        .build();
  1. 全局配置
// 2. 全局配置
GlobalConfig globalConfig = new GlobalConfig.Builder()
        .disableOpenDir() // 禁止自动打开输出目录
        .outputDir(dto.getOutputPath()) // 设置输出目录
        .author("xianren generater") // 设置作者名
        // .enableSwagger() // 开启 Swagger 模式
        .dateType(DateType.ONLY_DATE) // 设置时间类型策略
        .commentDate("yyyy-MM-dd") // 设置注释日期格式
        .build();
  1. 包配置

根据目录结构配置

// 3. 包配置
PackageConfig packageConfig = new PackageConfig.Builder()
        .parent(dto.getParentPackage()) // 设置父包名
        .entity(dto.getEntityPackage()) // 设置 Entity 包名
        .service(dto.getServicePackage()) // 设置 Service 包名
        .serviceImpl(dto.getServiceImplPackage()) // 设置 ServiceImpl 包名
        .mapper(dto.getMapperPackage()) // 设置 Mapper.java 包名
        .controller(dto.getControllerPackage()) // 设置 Controller 包名
        .pathInfo(Collections.singletonMap(OutputFile.xml, dto.getMapperXmlPath())) // 设置xml文件配置路径
        .build();
  1. 自定义配置

可以灵活拓展, 这里举例生成 dto、bo

// 4. 注入自定义配置, 注入 DTO 和 BO, 以及模板内部变量
ArrayList<CustomFile> customFileList = new ArrayList<>();
HashMap<String, Object> customPropMap = new HashMap<>();
// 赋值
setCustomFileAndCustomProp(dto, customFileList, customPropMap);
InjectionConfig injectionConfig = new InjectionConfig.Builder()
        .customFile(customFileList)
        .customMap(customPropMap)
        .build();

/**
 * 配置注入数据
 * @param confDto
 * @param customFileList
 * @param customPropMap
 */
private void setCustomFileAndCustomProp(ModelGeneratorDTO confDto, ArrayList<CustomFile> customFileList, HashMap<String, Object> customPropMap) {
    for (String tableName : confDto.getTableNameArr()) {
        // 表名称转大驼峰
        String finalTableName = StrUtil.upperFirst(StrUtil.toCamelCase(tableName));

        // dto 变量设置
        customPropMap.put("dtoClassName",finalTableName+"Dto");
        customPropMap.put("dtoPackage", confDto.getDtoPackage());
        // dto 模板文件配置
        customFileList.add(new CustomFile.Builder()
                .formatNameFunction(tableInfo -> finalTableName) // 文件名格式话
                .fileName("Dto.java")   // 文件名后缀
                .templatePath("/code-templates/dto.java.vm")
                .filePath(confDto.getOutputPath() + SEPARATOR
                        + confDto.getParentPackage().replaceAll("\\.",SEPARATOR)+SEPARATOR
                        + confDto.getDtoPackage().replaceAll("\\.",SEPARATOR))
                .build());

        // bo 变量设置
        customPropMap.put("boClassName",finalTableName+"Bo");
        customPropMap.put("boPackage", confDto.getBoPackage());
        // bo 模板文件配置
        customFileList.add(new CustomFile.Builder()
                .formatNameFunction(tableInfo -> finalTableName) // 文件名格式话
                .fileName("Bo.java")   // 文件名后缀
                .templatePath("/code-templates/bo.java.vm")
                .filePath(confDto.getOutputPath() + SEPARATOR
                        + confDto.getParentPackage().replaceAll("\\.",SEPARATOR)+SEPARATOR
                        + confDto.getBoPackage().replaceAll("\\.",SEPARATOR))
                .build());
    }
}

  1. 策略配置

策略中用来配置基础的 entity、service、controller、mapper 属性

// 5. 策略配置
StrategyConfig strategyConfig = new StrategyConfig.Builder()
        .addInclude(dto.getTableNameArr())   // 指定需要生成的表, 逗号分割
        // 实体类
        .entityBuilder()
        .javaTemplate("/code-templates/entity.java") // 设置实体类模板
        .formatFileName("%sEntity")
        .enableLombok()
        .idType(IdType.AUTO)
        // service
        .serviceBuilder()
        .serviceTemplate("/code-templates/service.java") // 设置 Service 模板
        .serviceImplTemplate("/code-templates/serviceImpl.java") // 设置 ServiceImpl 模板
        .formatServiceFileName("%sService")
        // controller
        .controllerBuilder()
        .template("/code-templates/controller.java")
        .enableRestStyle()
        // mapper
        .mapperBuilder()
        .enableBaseResultMap()// 启用xml文件中的BaseResultMap 生成
        .enableBaseColumnList()// 启用xml文件中的BaseColumnList
        .build();
  1. 构建生成器

引入上方的配置后调用execute()

// 6. 生成
AutoGenerator autoGenerator = new AutoGenerator(dataSourceConfig)
        .global(globalConfig)
        .packageInfo(packageConfig)
        .strategy(strategyConfig)
        .injection(injectionConfig);
autoGenerator.execute();

(四)vm模板配置

添加到目录 resources/code-templates 中

  1. controller.java.vm
package ${package.Controller};

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end

/**
 * <p>
 * $!{table.comment} 前端控制器
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
@Slf4j
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end

#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end

}
#end
  1. service.java.vm
package ${package.Service};

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

/**
 * <p>
 * $!{table.comment} 服务类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

}
#end

  1. serviceImpl.java.vm
package ${package.ServiceImpl};

import lombok.extern.slf4j.Slf4j;
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
#if(${generateService})
import ${package.Service}.${table.serviceName};
#end
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;

/**
 * <p>
 * $!{table.comment} 服务实现类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
@Slf4j
@Service
#if(${kotlin})
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()#if(${generateService}), ${table.serviceName}#end {

}
#else
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}>#if(${generateService}) implements ${table.serviceName}#end {

}
#end

  1. mapper.java.vm
package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};
#if(${mapperAnnotationClass})
import ${mapperAnnotationClass.name};
#end

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

}
#end

  1. mapper.xml.vm
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">

#if(${enableCache})
    <!-- 开启二级缓存 -->
    <cache type="${cacheClassName}"/>

#end
#if(${baseResultMap})
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
#foreach($field in ${table.fields})
#if(${field.keyFlag})##生成主键排在第一位
        <id column="${field.name}" property="${field.propertyName}" />
#end
#end
#foreach($field in ${table.commonFields})##生成公共字段
        <result column="${field.name}" property="${field.propertyName}" />
#end
#foreach($field in ${table.fields})
#if(!${field.keyFlag})##生成普通字段
        <result column="${field.name}" property="${field.propertyName}" />
#end
#end
    </resultMap>

#end
#if(${baseColumnList})
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
#foreach($field in ${table.commonFields})
        ${field.columnName},
#end
        ${table.fieldNames}
    </sql>

#end
</mapper>

  1. entity.java.vm
package ${package.Entity};

#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${springdoc})
import io.swagger.v3.oas.annotations.media.Schema;
#elseif(${swagger})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
#if(${chainModel})
import lombok.experimental.Accessors;
#end
#end

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

    private static final long serialVersionUID = 1L;
#end
## ----------  BEGIN 字段循环遍历  ----------
#foreach($field in ${table.fields})

#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
  #if(${springdoc})
    @Schema(description = "${field.comment}")
  #elseif(${swagger})
    @ApiModelProperty("${field.comment}")
  #else
    /**
     * ${field.comment}
     */
  #end
#end
#if(${field.keyFlag})
## 主键
  #if(${field.keyIdentityFlag})
    @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
  #elseif(!$null.isNull(${idType}) && "$!idType" != "")
    @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
  #elseif(${field.convert})
    @TableId("${field.annotationColumnName}")
  #end
## 普通字段
#elseif(${field.fill})
## -----   存在字段填充设置   -----
  #if(${field.convert})
    @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
  #else
    @TableField(fill = FieldFill.${field.fill})
  #end
#elseif(${field.convert})
    @TableField("${field.annotationColumnName}")
#end
## 乐观锁注解
#if(${field.versionField})
    @Version
#end
## 逻辑删除注解
#if(${field.logicDeleteField})
    @TableLogic
#end
    private ${field.propertyType} ${field.propertyName};
#end
## ----------  END 字段循环遍历  ----------
#if(!${entityLombokModel})
#foreach($field in ${table.fields})
  #if(${field.propertyType.equals("boolean")})
    #set($getprefix="is")
  #else
    #set($getprefix="get")
  #end

    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}) {
  #end
        this.${field.propertyName} = ${field.propertyName};
  #if(${chainModel})
        return this;
  #end
    }
#end
## --foreach end---
#end
## --end of #if(!${entityLombokModel})--
#if(${entityColumnConstant})
  #foreach($field in ${table.fields})

    public static final String ${field.name.toUpperCase()} = "${field.name}";
  #end
#end
#if(${activeRecord})

    @Override
    public Serializable pkVal() {
  #if(${keyPropertyName})
        return this.${keyPropertyName};
  #else
        return null;
  #end
    }
#end
#if(!${entityLombokModel})

    @Override
    public String toString() {
        return "${entity}{" +
  #foreach($field in ${table.fields})
    #if($!{foreach.index}==0)
        "${field.propertyName} = " + ${field.propertyName} +
    #else
        ", ${field.propertyName} = " + ${field.propertyName} +
    #end
  #end
        "}";
    }
#end
}

  1. bo.java.vm
package ${package.Parent}.${boPackage};

#if(${springdoc})
import io.swagger.v3.oas.annotations.media.Schema;
#elseif(${swagger})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
import lombok.Data;
import ${package.Entity}.${entity};


/**
 * <p>
 * $!{table.comment} BO
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */

@Data
## swagger配置
#if(${swagger})
@ApiModel(value = "${boClassName}对象", description = "$!{table.comment}")
#end
public class ${boClassName} extends ${entity} {
}
  1. dto.java.vm
package ${package.Parent}.${dtoPackage};

#if(${springdoc})
import io.swagger.v3.oas.annotations.media.Schema;
#elseif(${swagger})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
import lombok.Data;
import ${package.Entity}.${entity};


/**
 * <p>
 * $!{table.comment} DTO
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */

@Data
## swagger配置
#if(${swagger})
@ApiModel(value = "${dtoClassName}对象", description = "$!{table.comment}")
#end
public class ${dtoClassName} extends ${entity} {
}

(五)测试


/**
 * 根据数据库自动生成代码, 可以支持多张表
 * @throws SQLException
 */
public void generatorForSigleTable() throws SQLException {
    String projectPath = Paths.get(System.getProperty("user.dir")).toAbsolutePath().toString();

    // setter...
    ModelGeneratorDTO dto = ModelGeneratorDTO.builder()
            .tableNameArr("loan".split(","))
            .outputPath(projectPath)   // 文件输出位置
            .modelName("generator-model")    // 模块名称 (如果不是子服务的话制空)
            .parentPackage("cn.xianren.generator")  // 模块对应的包名(总项目总pom文件的包名)
            .compilePath("src/main/java")   // 代码编译源文件夹
            .entityPackage("bean.entity")
            .dtoPackage("bean.dto")
            .boPackage("bean.bo")
            .mapperPackage("mapper")
            .mapperXmlPath("src/main/resources/mapper")
            .servicePackage("service")
            .serviceImplPackage("service.impl")
            .controllerPackage("controller")
            .build();

    // 调整设置xml文件位置
    dto.setMapperXmlPath(dto.getModelName()+SEPARATOR+dto.getMapperXmlPath());
    // 生成文件的具体位置
    String outputDir = dto.getOutputPath()
            + SEPARATOR + dto.getModelName()
            + SEPARATOR + dto.getCompilePath();
    // 如果不是子模块
    if (ObjectUtil.isEmpty(dto.getModelName())){
        outputDir = dto.getOutputPath() + SEPARATOR + dto.getCompilePath();
    }
    dto.setOutputPath(outputDir);

    // 生成
    genExe(dto);
}


public static void main(String[] args) throws SQLException {
    GeneratorHandler handler = new GeneratorHandler();
    handler.generatorForSigleTable();
}

(六)运行效果示例

三、拓展方向

待后续编写下对应的前端页面, 更好的控制生成方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值