小demon
正常的项目架构
实际代码
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父依赖管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--项目的基本信息-->
<groupId>space.qiushui</groupId>
<artifactId>generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>generator</name>
<packaging>jar</packaging>
<description>generator</description>
<!--版本和属性的集中管理-->
<properties>
<java.version>11</java.version>
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
</properties>
<dependencies>
<!--web项目必需的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--美化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--提供数据库驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--Mybatis-plus的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--代码生成器的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--模版引擎 替换了默认的 VelocityEngine-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<!--引入原因:freemarker使用到了这个依赖对属性的判空操作-->
<dependency>
<groupId>org.realityforge.org.jetbrains.annotations</groupId>
<artifactId>org.jetbrains.annotations</artifactId>
<version>1.7.0</version>
</dependency>
<!--代码生成器globalConfig.enableSwagger会用到 enableSpringdoc不熟没用到-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--springboot封装的打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
CodeGenerator.java ——最简单的版本
package space.qiushui.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.*;
public class CodeGenerator {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/hai?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8";
private static final String USER_NAME = "root";
private static final String PASSWORD = "123067";
/**
* <p>mybatis-plus官网: <a href="https://baomidou.com/pages/779a6e/">官网</a></p>
* <p>mybatis-plus配置:<a href="https://baomidou.com/pages/981406/">配置</a></p>
*/
public static void main(String[] args) {
//项目路径获取
String projectPath = System.getProperty("user.dir");
FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
// 全局配置
.globalConfig((scanner, builder) -> builder
//不打开生成文件所在文件夹
.disableOpenDir()
// 文件生成目录 默认情况: win-> D:\ linux-> /temp
.outputDir(projectPath + "/src/main/java")
//创建者
.author("lhl")
//创建时间格式
.commentDate("yyyy-MM-dd")
//开启Swagger 模式 生成API文档
.enableSwagger()
//swagger在springboot 3.X 以后的替代方案 开启 Springdoc
//.enableSpringdoc()
//时间策略
.dateType(DateType.ONLY_DATE))
// 包配置 可配置模块 所有文件夹的名称
.packageConfig((scanner, builder) -> builder
//目录结构
.parent("space.qiushui.generator")
)
// 策略配置
.strategyConfig((scanner, builder) -> builder
// 引用控制台窗口输入表名称
.addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
//Controller 生成策略
.controllerBuilder()
//添加注解@RestController
.enableRestStyle()
.enableFileOverride()
//Entity 生成策略
.entityBuilder()
.enableLombok()
//禁用生成 serialVersionUID
.disableSerialVersionUID()
//开启生成实体时生成字段注解
//.enableTableFieldAnnotation()
//表映射到实体的命名策略: 默认下划线转驼峰命
.naming(NamingStrategy.underline_to_camel)
//格式化实体名称 影响controller等其他的命名
//.formatFileName("%sDO")
//service 生成策略
.serviceBuilder()
.enableFileOverride()
//格式化业务实现名称
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
//Mapper 生成策略
.mapperBuilder()
.enableFileOverride()
//开启 @Mapper 注解
.mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
//启用 BaseResultMap 生成
.enableBaseResultMap()
//启用 BaseColumnList
.enableBaseColumnList()
.build())
//模板引擎配置
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
/**
* 处理 all 情况
* @param tables 表名称
* @return 切割后的List集合
*/
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
}
升级版(自定义模板和属性)
覆盖原有controller、service和impl,生成通用业务方法 ——第二个版本
FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
// 全局配置
.globalConfig((scanner, builder) -> builder
//不打开生成文件所在文件夹
.disableOpenDir()
// 文件生成目录 默认情况: win-> D:\ linux-> /temp
.outputDir(projectPath + "/src/main/java")
//创建者
.author("lhl")
//创建时间格式
.commentDate("yyyy-MM-dd")
//开启Swagger 模式 生成API文档
.enableSwagger()
//swagger在springboot 3.X 以后的替代方案 开启 Springdoc
//.enableSpringdoc()
//时间策略
.dateType(DateType.ONLY_DATE))
// 包配置 可配置模块 所有文件夹的名称
.packageConfig((scanner, builder) -> builder
//目录结构
.parent("space.qiushui.generator")
)
// 策略配置
.strategyConfig((scanner, builder) -> builder
// 引用控制台窗口输入表名称
.addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
//Controller 生成策略
.controllerBuilder()
//添加注解@RestController
.enableRestStyle()
.enableFileOverride()
//Entity 生成策略
.entityBuilder()
.enableLombok()
//禁用生成 serialVersionUID
.disableSerialVersionUID()
//开启生成实体时生成字段注解
//.enableTableFieldAnnotation()
//表映射到实体的命名策略: 默认下划线转驼峰命
.naming(NamingStrategy.underline_to_camel)
//格式化实体名称 影响controller等其他的命名
//.formatFileName("%sDO")
//service 生成策略
.serviceBuilder()
.enableFileOverride()
//格式化业务实现名称
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
//Mapper 生成策略
.mapperBuilder()
.enableFileOverride()
//开启 @Mapper 注解
.mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
//启用 BaseResultMap 生成
.enableBaseResultMap()
//启用 BaseColumnList
.enableBaseColumnList()
.build())
//模板引擎配置
.templateEngine(new FreemarkerTemplateEngine())
//模版配置
.templateConfig((scanner, builder) -> builder
.controller("templates/controllerV2.java")
.service("templates/serviceV2.java")
.serviceImpl("templates/serviceImplV2.java")
)
//自定义模板配置
.injectionConfig(builder -> {
builder.beforeOutputFile(((tableInfo, stringObjectMap) -> {
//controller和service是否使用构造起注入
stringObjectMap.put("lombok", true);
//是否添加校验
stringObjectMap.put("validated", true);
}))
.build();
})
.execute();
模板在mybatis-plus-generator生成器中默认设置的路径是:src/main/resources/templates
controllerV2.java 模板
package ${package.Controller};
import org.springframework.web.bind.annotation.*;
<#if restControllerStyle>
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
<#if swagger>
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
</#if>
<#if lombok>
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
</#if>
<#if validated>
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
import org.springframework.validation.annotation.Validated;
</#if>
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
import java.lang.Long;
import java.util.List;
/**
* <p>
* ${table.comment!} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if swagger>
@Api(tags = {"${table.comment!}Controller"})
</#if>
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if lombok>
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
</#if>
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
private final ${table.serviceName} service;
<#if swagger>
@ApiOperation(value = "查询列表")
</#if>
@GetMapping(value = "/list")
public List<${entity}> list(${entity} query) {
List<${entity}> list = service.select${entity}List(query);
return list;
}
<#if swagger>
@ApiOperation(value = "查询详情")
</#if>
@GetMapping(value = "/{id}")
public ${entity} get${entity}ById(@PathVariable("id") Long id) {
return service.select${entity}ById(id);
}
<#if swagger>
@ApiOperation(value = "新增")
</#if>
@PostMapping
public ${entity} insert${entity}(<#if validated>@Validated(Insert.class)</#if> @RequestBody ${entity} req) {
return service.insert${entity}(req);
}
<#if swagger>
@ApiOperation(value = "修改")
</#if>
@PutMapping
public ${entity} update${entity}(<#if validated>@Validated(Update.class)</#if> @RequestBody ${entity} req) {
return service.update${entity}(req);
}
<#if swagger>
@ApiOperation(value = "删除")
</#if>
@DeleteMapping("/{ids}")
public List<Long> delete${entity}ByIds(@PathVariable(value = "ids") List<Long> ids) {
return service.delete${entity}ByIds(ids);
}
}
</#if>
serviceV2.java 模板
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
import java.lang.Long;
import java.util.List;
/**
* <p>
* ${table.comment!} 服务类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
/**
* 查询列表
*
* @param query 查询条件封装
* @return 查询结果
*/
List<${entity}> select${entity}List(${entity} query);
/**
* 通过ID查询结果
*
* @param id 主键ID
* @return 查询结果
*/
${entity} select${entity}ById(Long id);
/**
* 新增
*
* @param req 数据入参
* @return 操作结果
*/
${entity} insert${entity}(${entity} req);
/**
* 修改
*
* @param req 数据入参
* @return 操作结果
*/
${entity} update${entity}(${entity} req);
/**
* 批量删除操作
*
* @param ids 主键ID集合
* @return 操作结果
*/
List<Long> delete${entity}ByIds(List<Long> ids);
}
</#if>
serviceImplV2.java 模板
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
<#if table.serviceInterface>
import ${package.Service}.${table.serviceName};
</#if>
import ${superServiceImplClassPackage};
<#if lombok>
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
</#if>
import org.springframework.stereotype.Service;
import java.lang.Long;
import java.util.List;
/**
* <p>
* ${table.comment!} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if lombok>
@Slf4j
</#if>
@Service
<#if lombok>
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
</#if>
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()<#if table.serviceInterface>, ${table.serviceName}</#if> {
}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}><#if table.serviceInterface> implements ${table.serviceName}</#if> {
private final ${table.mapperName} mapper;
@Override
public List<${entity}> select${entity}List(${entity} query) {
return null;
}
@Override
public ${entity} select${entity}ById(Long id) {
return null;
}
@Override
public ${entity} insert${entity}(${entity} req) {
return null;
}
@Override
public ${entity} update${entity}(${entity} req) {
return null;
}
@Override
public List<Long> delete${entity}ByIds(List<Long> ids) {
return null;
}
}
</#if>
以上这些模板是放在
/src/main/resource/templates下的,文件命名如下:
controllerV2.java.ftlserviceV2.java.ftlserviceImplV2.java.ftl
将上面的模板文件代码复制到文件会代码格式缩进有问题里,以绝后患你可以自己调整和我页面上缩进就好,要么就是生成的文件controller、service、impl用快捷键Ctrl + Alt + L或Command + Option + L
自定义模板和属性 ——暂时的最终版本
FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
// 全局配置
.globalConfig((scanner, builder) -> builder
//不打开生成文件所在文件夹
.disableOpenDir()
// 文件生成目录 默认情况: win-> D:\ linux-> /temp
.outputDir(projectPath + "/src/main/java")
//创建者
.author("lhl")
//创建时间格式
.commentDate("yyyy-MM-dd")
//开启Swagger 模式 生成API文档
.enableSwagger()
//swagger在springboot 3.X 以后的替代方案 开启 Springdoc
//.enableSpringdoc()
//时间策略
.dateType(DateType.ONLY_DATE))
// 包配置 可配置模块 所有文件夹的名称
.packageConfig((scanner, builder) -> builder
//目录结构
.parent("space.qiushui.generator")
)
// 策略配置
.strategyConfig((scanner, builder) -> builder
// 引用控制台窗口输入表名称
.addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
//Controller 生成策略
.controllerBuilder()
//添加注解@RestController
.enableRestStyle()
.enableFileOverride()
//Entity 生成策略
.entityBuilder()
.enableLombok()
//禁用生成 serialVersionUID
.disableSerialVersionUID()
//开启生成实体时生成字段注解
//.enableTableFieldAnnotation()
//表映射到实体的命名策略: 默认下划线转驼峰命
.naming(NamingStrategy.underline_to_camel)
//格式化实体名称 影响controller等其他的命名
//.formatFileName("%sDO")
//service 生成策略
.serviceBuilder()
.enableFileOverride()
//格式化业务实现名称
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
//Mapper 生成策略
.mapperBuilder()
.enableFileOverride()
//开启 @Mapper 注解
.mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
//启用 BaseResultMap 生成
.enableBaseResultMap()
//启用 BaseColumnList
.enableBaseColumnList()
.build())
//模板引擎配置
.templateEngine(new FreemarkerTemplateEngine())
//模版配置
.templateConfig((scanner, builder) -> builder
.controller("templates/controllerV2.java")
.service("templates/serviceV2.java")
.serviceImpl("templates/serviceImplV2.java")
)
//自定义模板配置
.injectionConfig(builder -> {
//自定义文件
List<CustomFile> list = new ArrayList<>();
CustomFile convert = new CustomFile.Builder()
.enableFileOverride()
.formatNameFunction((tableInfo -> tableInfo.getEntityName() + "DTO"))
.fileName(".java")
.templatePath("templates/dto.java.ftl")
.packageName("dto")
.build();
//模板在上面,可以加其他的 covert[MapStruct] 和 DTO 的拆分
list.add(convert);
builder.customFile(list)
.beforeOutputFile(((tableInfo, stringObjectMap) -> {
//controller和service是否使用构造起注入
stringObjectMap.put("lombok", true);
//是否添加校验
stringObjectMap.put("validated", true);
//dto是否继承实体类
stringObjectMap.put("dtoExtends", true);
stringObjectMap.put("dtoName", tableInfo.getEntityName() + "DTO");
stringObjectMap.put("dtoPackage", "space.qiushui.generator.dto");
//无尽想象...
}))
.build();
})
.execute();
具体的模板 dto.java:
package ${dtoPackage};
import java.io.Serializable;
<#if springdoc>
import io.swagger.v3.oas.annotations.media.Schema;
<#elseif swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if dtoExtends>
import ${package.Entity}.${entity};
</#if>
<#if entityLombokModel>
<#if dtoExtends>
import lombok.EqualsAndHashCode;
</#if>
import lombok.Getter;
import lombok.Setter;
<#if chainModel>
import lombok.experimental.Accessors;
</#if>
</#if>
/**
* <p>
* ${table.comment!} 数据传输类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Getter
@Setter
<#if chainModel>
@Accessors(chain = true)
</#if>
<#if dtoExtends>
@EqualsAndHashCode(callSuper = true)
</#if>
</#if>
<#if springdoc>
@Schema(name = "${entity}数据传输类", description = "${table.comment!}")
<#elseif swagger>
@ApiModel(value = "${entity}数据传输类", description = "${table.comment!}")
</#if>
<#if dtoExtends>
public class ${dtoName} extends ${entity} implements Serializable {
<#else>
public class ${dtoName} implements Serializable {
</#if>
private static final long serialVersionUID = 1L;
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#if dtoExtends>
<#if springdoc>
@Schema(description = "拓展字段")
<#elseif swagger>
@ApiModelProperty("拓展字段")
<#else>
/**
* 拓展字段示例
*/
</#if>
private String extra;
<#else>
<#list table.fields as field>
<#if field.comment!?length gt 0>
<#if springdoc>
@Schema(description = "${field.comment}")
<#elseif swagger>
@ApiModelProperty("${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
</#if>
<#------------ 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 !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>
}
备注:文件内容和文件名称要相互映照,因为是有相互引用的,不然会导致错乱哦
项目创建步骤
一切的基础前提:
- jdk下载:1.8版本或更高,我的是11,并且安装好,配置好环境变量。
- idea下载(可选):可以用记事本硬写也行,推荐idea。
- maven下载(可选):配置好环境变量,配置好setting文件,复制一个备份到.m2下。
- 数据库要有MySQL/H2都行
-
项目初始化:SpringBoot初始化项目
-
替换pom文件和将Generator.java文件,随意放在 /src/main/java 下随便哪个位置都行
-
修改项目jdk版本和配置maven设置,然后加载依赖就好
- 点击Generator.java中的main方法运行,输入表名,就完成了
功能讲解
官网信息
解释
为什么使用springboot:2.7.18
原因:请看SpringBoot官方文档,而且我建这个 Demon 肯定是拿这个版本相应最新的东西来了解,又没有什么业务和配置啥的,需要妥协版本的。所以想以此作为一个阶段,后面跃升到对应的17和21啊。
其实还可以适当的看看
mybatis-plus-generator和freemarker的源码实现和API,很懒,不想写了,这个demon就到这了啊,使用可以通过自定义属性和自定义文件夹,将通用的功能借助这个现有的代码生成来玩,就好了。拜拜