一、简介
使用 velocity 模板引擎 + mybatis-plus-generator 快速生成代码
(一)功能介绍
自动生成 controller、service、mapper、entity、dto、bo
根据数据库连接信息灵活生成基于spring boot 的代码, 扩展性高, 可以适配多模块项目, 也可以脱离spring boot运行
目前支持功能:
- 可以根据模板引擎自定义代码生成样式
- 自定义代码存放目录
- 支持跨模块生成
- 支持多种数据库
(二)设计技术栈
- spring boot
- mybatis-plus-generator
- 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. 代码生成器的数据库配置
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(dataSource)
.dbQuery(new MySqlQuery()) //
.typeConvert(new MySqlTypeConvert()) // 类型转换配置
.keyWordsHandler(new MySqlKeyWordsHandler()) // 关键字配置
.databaseQueryClass(SQLQuery.class) // 设置查询实例
.build();
- 全局配置
// 2. 全局配置
GlobalConfig globalConfig = new GlobalConfig.Builder()
.disableOpenDir() // 禁止自动打开输出目录
.outputDir(dto.getOutputPath()) // 设置输出目录
.author("xianren generater") // 设置作者名
// .enableSwagger() // 开启 Swagger 模式
.dateType(DateType.ONLY_DATE) // 设置时间类型策略
.commentDate("yyyy-MM-dd") // 设置注释日期格式
.build();
- 包配置
根据目录结构配置
// 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();
- 自定义配置
可以灵活拓展, 这里举例生成 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());
}
}
- 策略配置
策略中用来配置基础的 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();
- 构建生成器
引入上方的配置后调用
execute()
// 6. 生成
AutoGenerator autoGenerator = new AutoGenerator(dataSourceConfig)
.global(globalConfig)
.packageInfo(packageConfig)
.strategy(strategyConfig)
.injection(injectionConfig);
autoGenerator.execute();
(四)vm模板配置
添加到目录 resources/code-templates 中
- 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
- 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
- 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
- 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
- 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>
- 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
}
- 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} {
}
- 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();
}
(六)运行效果示例
三、拓展方向
待后续编写下对应的前端页面, 更好的控制生成方式。