极速提升开发效率:Spring Boot + MyBatis-Plus的代码生成器配置、定制与使用
·
假设你现在正在负责某个系统的开发工作,按业务功能至少设计了50张表,每张表都需要实现标准的CRUD接口。如果手工编写:
50个实体类
50个Mapper接口
50个Service接口和ServiceImpl实现
50个Controller
以及对应的XML映射文件
这至少需要1-2周的时间,而且容易出错,且更多是重复劳动。
如果改为使用 MyBatis-Plus 代码生成器,只需要5-10分钟配置,就能生成所有基础代码,而且风格统一、规范一致。
一、本文所使用技术栈版本
| 组件 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 3.2.5 | Spring最新稳定版,支持JDK 17+ |
| JDK | 17 | 企业级LTS版本,使用Record等新特性 |
| MyBatis-Plus | 3.5.6 | 最新稳定版,代码生成器功能完善 |
| MySQL | 8.0.x | MySQL 8.0+,支持JSON等新特性 |
| Maven | 3.9.x | 项目管理工具 |
| SpringDoc | 2.5.0 | OpenAPI 3.0文档生成 |
二、项目环境搭建
2.1 创建Spring Boot项目
<?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>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>code-generator-demo</artifactId>
<version>1.0.0</version>
<name>code-generator-demo</name>
<description>MyBatis-Plus代码生成器示例</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<springdoc.version>2.5.0</springdoc.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MyBatis-Plus代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Freemarker模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SpringDoc OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<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>
2.2 配置文件 application.yml
# 服务配置
server:
port: 8080
servlet:
context-path: /api
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024
# Spring配置
spring:
application:
name: code-generator-demo
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
hikari:
connection-timeout: 30000
maximum-pool-size: 20
minimum-idle: 5
max-lifetime: 1800000
connection-test-query: SELECT 1
# 数据库初始化
sql:
init:
mode: always
schema-locations: classpath:schema.sql
data-locations: classpath:data.sql
# Freemarker配置
freemarker:
suffix: .ftl
charset: UTF-8
template-loader-path: classpath:/templates/
# MyBatis-Plus配置
mybatis-plus:
# 支持通配符
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
# 主键类型
id-type: auto
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 0
configuration:
# 开启驼峰命名
map-underscore-to-camel-case: true
# 日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 缓存
cache-enabled: true
# 日志配置
logging:
level:
root: INFO
com.example.demo: DEBUG
com.baomidou.mybatisplus: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:
name: logs/app.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
file-name-pattern: logs/app.%d{yyyy-MM-dd}.%i.log
# SpringDoc配置
springdoc:
api-docs:
path: /v3/api-docs
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
operations-sorter: alpha
tags-sorter: alpha
三、代码生成器核心配置
3.1 创建测试数据库
-- 创建数据库
CREATE DATABASE IF NOT EXISTS demo_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE demo_db;
-- 用户表
CREATE TABLE IF NOT EXISTS t_user (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
gender TINYINT COMMENT '性别:1-男,2-女',
age INT COMMENT '年龄',
avatar VARCHAR(500) COMMENT '头像',
last_login_time DATETIME COMMENT '最后登录时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除',
version INT DEFAULT 0 COMMENT '版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE IF NOT EXISTS t_role (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
role_name VARCHAR(100) NOT NULL COMMENT '角色名称',
description VARCHAR(500) COMMENT '描述',
sort INT DEFAULT 0 COMMENT '排序',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
PRIMARY KEY (id),
UNIQUE KEY uk_role_code (role_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 插入测试数据
INSERT INTO t_user (username, password, email, phone, status, gender, age) VALUES
('admin', '123456', 'admin@example.com', '13800138000', 1, 1, 30),
('user1', '123456', 'user1@example.com', '13800138001', 1, 2, 25),
('user2', '123456', 'user2@example.com', '13800138002', 0, 1, 28);
INSERT INTO t_role (role_code, role_name, description, sort) VALUES
('ROLE_ADMIN', '管理员', '系统管理员', 1),
('ROLE_USER', '普通用户', '普通用户', 2),
('ROLE_GUEST', '访客', '访客角色', 3);
3.2 代码生成器核心类
package com.example.demo.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* MyBatis-Plus代码生成器配置类
* 企业级最佳实践,包含完整的配置和注释
*
* 注意:生成后需要手动运行,或设置为CommandLineRunner自动运行
*
* @author 示例作者
*/
@Slf4j
@Component
public class CodeGenerator implements CommandLineRunner {
/**
* 数据库连接配置
*/
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
/**
* 项目配置
*/
// 项目根路径
private static final String PROJECT_PATH = System.getProperty("user.dir");
// 作者
private static final String AUTHOR = "示例作者";
// 包名
private static final String PACKAGE_NAME = "com.example.demo";
// 模块名(如果有多模块)
private static final String MODULE_NAME = "";
@Override
public void run(String... args) throws Exception {
// 注释掉下面这行,避免每次启动都生成代码
// generateCode();
}
/**
* 生成所有表的代码
*/
public void generateAllTables() {
log.info("开始生成所有表的代码...");
generateCode(null);
}
/**
* 生成指定表的代码
* @param tableNames 表名数组,为null时生成所有表
*/
public void generateTables(String... tableNames) {
log.info("开始生成指定表的代码: {}", (Object) tableNames);
generateCode(tableNames);
}
/**
* 核心代码生成方法
* @param tableNames 要生成代码的表名
*/
private void generateCode(String... tableNames) {
long startTime = System.currentTimeMillis();
try {
// 1. 创建代码生成器
FastAutoGenerator.create(url, username, password)
// 2. 全局配置
.globalConfig(builder -> {
builder
// 设置作者
.author(AUTHOR)
// 输出目录
.outputDir(PROJECT_PATH + "/src/main/java")
// 开启swagger支持
.enableSwagger()
// 日期类型
.dateType(DateType.TIME_PACK)
// 注释日期格式
.commentDate("yyyy-MM-dd HH:mm:ss")
// 禁止打开输出目录
.disableOpenDir()
// 开启kotlin模式(可选)
// .enableKotlin()
// 覆盖文件
.fileOverride();
})
// 3. 包配置
.packageConfig(builder -> {
builder
// 父包名
.parent(PACKAGE_NAME)
// 模块名
.moduleName(MODULE_NAME)
// Entity包名
.entity("entity")
// Mapper包名
.mapper("mapper")
// Service包名
.service("service")
// Service实现类包名
.serviceImpl("service.impl")
// Controller包名
.controller("controller")
// Mapper XML路径
.pathInfo(Collections.singletonMap(
OutputFile.xml,
PROJECT_PATH + "/src/main/resources/mapper"
));
})
// 4. 策略配置
.strategyConfig(builder -> {
builder
// 设置需要生成的表名
.addInclude(tableNames != null ? tableNames : getAllTables())
// 设置过滤表前缀
.addTablePrefix("t_", "sys_", "biz_")
// 设置过滤字段前缀
.addFieldPrefix("is_", "has_", "can_")
// Entity策略配置
.entityBuilder()
// 开启Lombok
.enableLombok()
// 开启链式模型
.enableChainModel()
// 开启ActiveRecord模式
.enableActiveRecord()
// 逻辑删除字段名
.logicDeleteColumnName("deleted")
// 逻辑删除属性名
.logicDeletePropertyName("deleted")
// 乐观锁字段名
.versionColumnName("version")
// 乐观锁属性名
.versionPropertyName("version")
// 数据库表映射到实体的命名策略
.naming(NamingStrategy.underline_to_camel)
// 数据库表字段映射到实体的命名策略
.columnNaming(NamingStrategy.underline_to_camel)
// 添加SuperClass
// .superClass(BaseEntity.class)
// 添加@TableField注解
.enableTableFieldAnnotation()
// 开启生成字段常量
.enableColumnConstant()
// 开启生成serialVersionUID
.enableSerialVersionUID()
// 格式化文件名称
.formatFileName("%s")
// Controller策略配置
.controllerBuilder()
// 开启生成@RestController控制器
.enableRestStyle()
// 开启驼峰转连字符
.enableHyphenStyle()
// 开启生成@RequestBody注解
.enableContentType()
// 格式化文件名称
.formatFileName("%sController")
// Service策略配置
.serviceBuilder()
// 格式化service接口文件名称
.formatServiceFileName("%sService")
// 格式化service实现类文件名称
.formatServiceImplFileName("%sServiceImpl")
// Mapper策略配置
.mapperBuilder()
// 开启@Mapper注解
.enableMapperAnnotation()
// 开启BaseResultMap生成
.enableBaseResultMap()
// 开启BaseColumnList
.enableBaseColumnList()
// 格式化mapper文件名称
.formatMapperFileName("%sMapper")
// 格式化xml文件名称
.formatXmlFileName("%sMapper");
})
// 5. 模板配置
.templateEngine(new EnhancedFreemarkerTemplateEngine())
// 6. 注入配置
.injectionConfig(builder -> {
// 自定义属性注入
Map<String, Object> customMap = new HashMap<>();
customMap.put("author", AUTHOR);
customMap.put("version", "1.0.0");
customMap.put("company", "Spring Boot Tutorial");
builder
.customMap(customMap)
// 自定义文件输出
.beforeOutputFile((tableInfo, objectMap) -> {
log.info("正在生成表: {}", tableInfo.getEntityName());
});
})
// 7. 执行生成
.execute();
long endTime = System.currentTimeMillis();
log.info("代码生成完成!耗时: {} ms", endTime - startTime);
} catch (Exception e) {
log.error("代码生成失败: ", e);
throw new RuntimeException("代码生成失败", e);
}
}
/**
* 获取所有表名
*/
private String[] getAllTables() {
// 这里可以根据实际情况返回所有表名
// 为了演示,我们返回示例表
return new String[]{"t_user", "t_role"};
}
}
3.3 自定义模板引擎
package com.example.demo.generator;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Map;
/**
* 增强的Freemarker模板引擎
* 支持自定义模板路径和自定义模板
*/
public class EnhancedFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
/**
* 自定义输出文件
* 可以在这里自定义生成额外的文件
*/
@Override
protected void outputCustomFile(
@NotNull Map<String, String> customFile,
@NotNull TableInfo tableInfo,
@NotNull ConfigBuilder configBuilder
) {
// 生成自定义文件
String entityName = tableInfo.getEntityName();
String parentPackage = configBuilder.getPackageInfo().getParent();
// 1. 生成DTO
outputDTO(entityName, parentPackage, tableInfo, configBuilder);
// 2. 生成VO
outputVO(entityName, parentPackage, tableInfo, configBuilder);
// 3. 生成Query
outputQuery(entityName, parentPackage, tableInfo, configBuilder);
// 调用父类方法生成其他自定义文件
super.outputCustomFile(customFile, tableInfo, configBuilder);
}
/**
* 生成DTO类
*/
private void outputDTO(String entityName, String parentPackage,
TableInfo tableInfo, ConfigBuilder configBuilder) {
String dtoPackage = parentPackage + ".dto";
String dtoPath = getPathInfo(configBuilder, OutputFile.entity);
String dtoFilePath = dtoPath.replace(
configBuilder.getPackageInfo().getEntity().replace(".", "/"),
dtoPackage.replace(".", "/")
) + File.separator + entityName + "DTO.java";
Map<String, Object> objectMap = getObjectMap(configBuilder, tableInfo);
objectMap.put("package", dtoPackage);
objectMap.put("entityName", entityName);
objectMap.put("className", entityName + "DTO");
objectMap.put("tableInfo", tableInfo);
try {
outputFile(new File(dtoFilePath), objectMap,
"/templates/dto.java.ftl");
} catch (Exception e) {
logger.error("生成DTO失败: " + entityName, e);
}
}
/**
* 生成VO类
*/
private void outputVO(String entityName, String parentPackage,
TableInfo tableInfo, ConfigBuilder configBuilder) {
String voPackage = parentPackage + ".vo";
String voPath = getPathInfo(configBuilder, OutputFile.entity);
String voFilePath = voPath.replace(
configBuilder.getPackageInfo().getEntity().replace(".", "/"),
voPackage.replace(".", "/")
) + File.separator + entityName + "VO.java";
Map<String, Object> objectMap = getObjectMap(configBuilder, tableInfo);
objectMap.put("package", voPackage);
objectMap.put("entityName", entityName);
objectMap.put("className", entityName + "VO");
objectMap.put("tableInfo", tableInfo);
try {
outputFile(new File(voFilePath), objectMap,
"/templates/vo.java.ftl");
} catch (Exception e) {
logger.error("生成VO失败: " + entityName, e);
}
}
/**
* 生成Query类
*/
private void outputQuery(String entityName, String parentPackage,
TableInfo tableInfo, ConfigBuilder configBuilder) {
String queryPackage = parentPackage + ".query";
String queryPath = getPathInfo(configBuilder, OutputFile.entity);
String queryFilePath = queryPath.replace(
configBuilder.getPackageInfo().getEntity().replace(".", "/"),
queryPackage.replace(".", "/")
) + File.separator + entityName + "Query.java";
Map<String, Object> objectMap = getObjectMap(configBuilder, tableInfo);
objectMap.put("package", queryPackage);
objectMap.put("entityName", entityName);
objectMap.put("className", entityName + "Query");
objectMap.put("tableInfo", tableInfo);
try {
outputFile(new File(queryFilePath), objectMap,
"/templates/query.java.ftl");
} catch (Exception e) {
logger.error("生成Query失败: " + entityName, e);
}
}
/**
* 获取路径信息
*/
private String getPathInfo(ConfigBuilder configBuilder, OutputFile outputFile) {
Map<OutputFile, String> pathInfo = configBuilder.getPathInfo();
return pathInfo.get(outputFile);
}
}
四、完整代码生成示例
4.1 创建自定义模板
在 src/main/resources/templates/ 目录下创建以下模板文件:
1. 实体类模板 (entity.java.ftl)
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.*;
</#if>
/**
* ${table.comment!} 实体类
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
<#if chainModel>
@Accessors(chain = true)
</#if>
@Builder
@NoArgsConstructor
@AllArgsConstructor
</#if>
<#if swagger>
@Schema(description = "${table.comment!}")
</#if>
<#if table.convert>
@TableName("${schemaName}${table.name}")
<#elseif activeRecord>
@TableName("${schemaName}${table.name}")
</#if>
public class ${entity} {
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
/**
* ${field.comment}
*/
</#if>
<#if swagger>
@Schema(description = "${field.comment}")
</#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 (versionFieldName!"") == field.name>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if (logicDeleteFieldName!"") == field.name>
@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>
}
2. DTO模板 (dto.java.ftl)
package ${package};
<#if swagger>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
</#if>
import jakarta.validation.constraints.*;
import java.io.Serializable;
<#list table.fields as field>
<#if field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">
import java.time.${field.propertyType};
<#break>
</#if>
</#list>
/**
* ${table.comment!} DTO
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
</#if>
<#if swagger>
@Schema(description = "${table.comment!}请求参数")
</#if>
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
<#list table.fields as field>
<#if field.comment!?length gt 0>
/**
* ${field.comment}
*/
</#if>
<#if swagger>
@Schema(description = "${field.comment}")
</#if>
<#if field.propertyName == "id">
<#-- ID字段不校验 -->
<#elseif field.propertyName == "username" || field.propertyName == "name">
@NotBlank(message = "${field.comment}不能为空")
@Size(min = 2, max = 50, message = "${field.comment}长度必须在2-50之间")
<#elseif field.propertyName == "email">
@NotBlank(message = "${field.comment}不能为空")
@Email(message = "${field.comment}格式不正确")
<#elseif field.propertyName == "phone">
@NotBlank(message = "${field.comment}不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "${field.comment}格式不正确")
<#elseif field.propertyName?ends_with("Time")>
<#-- 时间字段不校验 -->
<#elseif field.propertyName == "status">
@NotNull(message = "${field.comment}不能为空")
@Min(value = 0, message = "${field.comment}最小值为0")
@Max(value = 1, message = "${field.comment}最大值为1")
<#elseif field.propertyName == "sort" || field.propertyName == "age">
@Min(value = 0, message = "${field.comment}不能小于0")
<#elseif field.propertyType == "String">
@Size(max = 500, message = "${field.comment}长度不能超过500")
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
}
3. VO模板 (vo.java.ftl)
package ${package};
<#if swagger>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
</#if>
import java.io.Serializable;
<#list table.fields as field>
<#if field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">
import java.time.${field.propertyType};
<#break>
</#if>
</#list>
/**
* ${table.comment!} VO
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
</#if>
<#if swagger>
@Schema(description = "${table.comment!}响应数据")
</#if>
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
<#list table.fields as field>
<#if field.comment!?length gt 0>
/**
* ${field.comment}
*/
</#if>
<#if swagger>
@Schema(description = "${field.comment}")
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#-- 添加状态名称转换 -->
<#list table.fields as field>
<#if field.propertyName == "status">
@Schema(description = "状态名称")
public String getStatusName() {
if (status == null) {
return "未知";
}
switch (status) {
case 0: return "禁用";
case 1: return "启用";
default: return "未知";
}
}
<#break>
</#if>
</#list>
}
4. Query模板 (query.java.ftl)
package ${package};
<#if swagger>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
</#if>
import java.io.Serializable;
<#list table.fields as field>
<#if field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">
import java.time.${field.propertyType};
<#break>
</#if>
</#list>
/**
* ${table.comment!} 查询参数
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
</#if>
<#if swagger>
@Schema(description = "${table.comment!}查询参数")
</#if>
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "页码", example = "1")
private Integer pageNum = 1;
@Schema(description = "每页数量", example = "10")
private Integer pageSize = 10;
<#list table.fields as field>
<#if field.propertyName != "id" &&
field.propertyName != "deleted" &&
field.propertyName != "version" &&
!field.propertyName?ends_with("Time")>
<#if field.comment!?length gt 0>
/**
* ${field.comment}
*/
</#if>
<#if swagger>
@Schema(description = "${field.comment}")
</#if>
private ${field.propertyType} ${field.propertyName};
</#if>
</#list>
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "排序字段")
private String orderBy = "create_time";
@Schema(description = "排序方向: asc/desc")
private String orderDirection = "desc";
}
5. 自定义Controller模板 (controller.java.ftl)
package ${package.Controller};
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import ${cfg.dtoPackage}.${entity}DTO;
import ${cfg.voPackage}.${entity}VO;
import ${cfg.queryPackage}.${entity}Query;
import com.example.demo.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* ${table.comment!} 控制器
*
* @author ${author}
* @since ${date}
*/
@Slf4j
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/${cfg.apiVersion}/${table.entityPath}")
@Tag(name = "${table.comment!}", description = "${table.comment!}管理接口")
public class ${table.controllerName} {
private final ${table.serviceName} ${table.serviceName?uncap_first};
@Operation(summary = "分页查询${table.comment!}")
@GetMapping("/page")
public Result<IPage<${entity}VO>> page(@Valid ${entity}Query query) {
log.info("分页查询${table.comment!}, 参数: {}", query);
IPage<${entity}VO> result = ${table.serviceName?uncap_first}.page(query);
return Result.success(result);
}
@Operation(summary = "查询${table.comment!}列表")
@GetMapping("/list")
public Result<List<${entity}VO>> list(@Valid ${entity}Query query) {
log.info("查询${table.comment!}列表, 参数: {}", query);
List<${entity}VO> result = ${table.serviceName?uncap_first}.list(query);
return Result.success(result);
}
@Operation(summary = "根据ID查询${table.comment!}")
@GetMapping("/{id}")
public Result<${entity}VO> getById(
@Parameter(description = "ID") @PathVariable Long id) {
log.info("根据ID查询${table.comment!}, id: {}", id);
${entity}VO result = ${table.serviceName?uncap_first}.getById(id);
return Result.success(result);
}
@Operation(summary = "新增${table.comment!}")
@PostMapping
public Result<Void> create(@Valid @RequestBody ${entity}DTO dto) {
log.info("新增${table.comment!}, 参数: {}", dto);
${table.serviceName?uncap_first}.create(dto);
return Result.success();
}
@Operation(summary = "更新${table.comment!}")
@PutMapping("/{id}")
public Result<Void> update(
@Parameter(description = "ID") @PathVariable Long id,
@Valid @RequestBody ${entity}DTO dto) {
log.info("更新${table.comment!}, id: {}, 参数: {}", id, dto);
${table.serviceName?uncap_first}.update(id, dto);
return Result.success();
}
@Operation(summary = "删除${table.comment!}")
@DeleteMapping("/{id}")
public Result<Void> delete(@Parameter(description = "ID") @PathVariable Long id) {
log.info("删除${table.comment!}, id: {}", id);
${table.serviceName?uncap_first}.delete(id);
return Result.success();
}
@Operation(summary = "批量删除${table.comment!}")
@DeleteMapping("/batch")
public Result<Void> batchDelete(@RequestBody List<Long> ids) {
log.info("批量删除${table.comment!}, ids: {}", ids);
${table.serviceName?uncap_first}.batchDelete(ids);
return Result.success();
}
}
4.2 创建生成器启动类
package com.example.demo;
import com.example.demo.generator.CodeGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* 代码生成器应用启动类
* 注意:生成代码后,请注释掉@Bean配置,避免每次启动都生成
*/
@Slf4j
@SpringBootApplication
public class CodeGeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(CodeGeneratorApplication.class, args);
}
/**
* 配置代码生成器Bean
* 注意:生成代码后,请注释掉这个方法,避免每次启动都生成
*/
@Bean
public ApplicationRunner codeGeneratorRunner(CodeGenerator codeGenerator) {
return args -> {
// 生成指定表的代码
// codeGenerator.generateTables("t_user", "t_role");
// 或者生成所有表的代码
// codeGenerator.generateAllTables();
log.info("代码生成器已配置,如需生成代码请取消注释上面的方法");
};
}
}
4.3 运行代码生成器
创建测试类来运行代码生成器:
package com.example.demo.test;
import com.example.demo.generator.CodeGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
/**
* 代码生成器测试类
* 运行此测试类来生成代码
*/
@SpringBootTest
@ActiveProfiles("dev")
class CodeGeneratorTest {
@Autowired
private CodeGenerator codeGenerator;
@Test
void testGenerateCode() {
// 生成用户表和角色表的代码
codeGenerator.generateTables("t_user", "t_role");
// 生成后的文件结构:
// src/main/java/com/example/demo/
// ├── entity/
// │ ├── User.java
// │ └── Role.java
// ├── mapper/
// │ ├── UserMapper.java
// │ └── RoleMapper.java
// ├── service/
// │ ├── UserService.java
// │ └── RoleService.java
// ├── service/impl/
// │ ├── UserServiceImpl.java
// │ └── RoleServiceImpl.java
// ├── controller/
// │ ├── UserController.java
// │ └── RoleController.java
// ├── dto/
// │ ├── UserDTO.java
// │ └── RoleDTO.java
// ├── vo/
// │ ├── UserVO.java
// │ └── RoleVO.java
// └── query/
// ├── UserQuery.java
// └── RoleQuery.java
}
}
运行结果:
2024-01-15 10:30:25.123 [main] INFO c.e.demo.generator.CodeGenerator - 开始生成指定表的代码: [t_user, t_role]
2024-01-15 10:30:25.456 [main] INFO c.e.demo.generator.CodeGenerator - 正在生成表: User
2024-01-15 10:30:25.567 [main] INFO c.e.demo.generator.CodeGenerator - 正在生成表: Role
2024-01-15 10:30:26.789 [main] INFO c.e.demo.generator.CodeGenerator - 代码生成完成!耗时: 1567 ms
4.4 查看生成的代码
生成的User实体类示例:
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户表 实体类
*
* @author 示例作者
* @since 2024-01-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "用户表")
@TableName("t_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@Schema(description = "主键ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
@Schema(description = "用户名")
@TableField("username")
private String username;
/**
* 密码
*/
@Schema(description = "密码")
@TableField("password")
private String password;
/**
* 邮箱
*/
@Schema(description = "邮箱")
@TableField("email")
private String email;
/**
* 手机号
*/
@Schema(description = "手机号")
@TableField("phone")
private String phone;
/**
* 状态:0-禁用,1-启用
*/
@Schema(description = "状态:0-禁用,1-启用")
@TableField("status")
private Integer status;
/**
* 性别:1-男,2-女
*/
@Schema(description = "性别:1-男,2-女")
@TableField("gender")
private Integer gender;
/**
* 年龄
*/
@Schema(description = "年龄")
@TableField("age")
private Integer age;
/**
* 头像
*/
@Schema(description = "头像")
@TableField("avatar")
private String avatar;
/**
* 最后登录时间
*/
@Schema(description = "最后登录时间")
@TableField("last_login_time")
private LocalDateTime lastLoginTime;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@Schema(description = "更新时间")
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除:0-未删除,1-已删除
*/
@Schema(description = "逻辑删除:0-未删除,1-已删除")
@TableLogic
private Integer deleted;
/**
* 版本号
*/
@Schema(description = "版本号")
@Version
private Integer version;
}
五、企业级定制技巧
5.1 添加自定义注释
/**
* 自定义代码生成器 - 添加表注释和字段注释
*/
public class CustomCodeGenerator {
/**
* 获取数据库表注释
*/
public String getTableComment(Connection connection, String tableName) throws SQLException {
String sql = "SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, tableName);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("TABLE_COMMENT");
}
}
}
return "";
}
/**
* 获取字段注释
*/
public Map<String, String> getColumnComments(Connection connection, String tableName) throws SQLException {
Map<String, String> comments = new HashMap<>();
String sql = "SELECT COLUMN_NAME, COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, tableName);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
comments.put(rs.getString("COLUMN_NAME"), rs.getString("COLUMN_COMMENT"));
}
}
}
return comments;
}
}
5.2 支持多数据源
/**
* 多数据源代码生成器
*/
@Component
public class MultiDataSourceCodeGenerator {
@Value("${spring.datasource.primary.url}")
private String primaryUrl;
@Value("${spring.datasource.primary.username}")
private String primaryUsername;
@Value("${spring.datasource.primary.password}")
private String primaryPassword;
@Value("${spring.datasource.secondary.url}")
private String secondaryUrl;
@Value("${spring.datasource.secondary.username}")
private String secondaryUsername;
@Value("${spring.datasource.secondary.password}")
private String secondaryPassword;
/**
* 生成主数据源代码
*/
public void generatePrimaryTables(String... tableNames) {
generateCode(primaryUrl, primaryUsername, primaryPassword, tableNames);
}
/**
* 生成从数据源代码
*/
public void generateSecondaryTables(String... tableNames) {
generateCode(secondaryUrl, secondaryUsername, secondaryPassword, tableNames);
}
private void generateCode(String url, String username, String password, String... tableNames) {
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author("System")
.outputDir(PROJECT_PATH + "/src/main/java")
.enableSwagger()
.dateType(DateType.TIME_PACK)
.commentDate("yyyy-MM-dd")
.disableOpenDir();
})
.packageConfig(builder -> {
builder.parent("com.example.demo")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.pathInfo(Collections.singletonMap(
OutputFile.xml,
PROJECT_PATH + "/src/main/resources/mapper"
));
})
.strategyConfig(builder -> {
builder.addInclude(tableNames)
.addTablePrefix("t_")
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
.controllerBuilder()
.enableRestStyle()
.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl");
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
5.3 添加自定义方法
/**
* 自定义Service模板
* 在Service接口中添加自定义方法
*/
public class CustomServiceTemplateEngine extends FreemarkerTemplateEngine {
@Override
public Map<String, Object> getObjectMap(ConfigBuilder configBuilder,
TableInfo tableInfo) {
Map<String, Object> objectMap = super.getObjectMap(configBuilder, tableInfo);
// 添加自定义方法
List<String> customMethods = new ArrayList<>();
customMethods.add(" /**");
customMethods.add(" * 根据ID列表查询");
customMethods.add(" */");
customMethods.add(" List<" + tableInfo.getEntityName() + "> listByIds(List<Long> ids);");
customMethods.add("");
customMethods.add(" /**");
customMethods.add(" * 根据条件统计数量");
customMethods.add(" */");
customMethods.add(" long countByCondition(" + tableInfo.getEntityName() + "Query query);");
objectMap.put("customMethods", String.join("\n", customMethods));
return objectMap;
}
}
六、常见问题与解决方案
6.1 问题1:生成的代码不符合公司规范
解决方案:自定义模板
/**
* 公司规范模板配置
*/
public class CompanyTemplateConfig {
/**
* 配置公司规范的模板
*/
public void configCompanyTemplates() {
// 1. 自定义实体类模板
Map<String, String> customTemplate = new HashMap<>();
customTemplate.put("entity.java", "/templates/company/entity.java.ftl");
customTemplate.put("mapper.java", "/templates/company/mapper.java.ftl");
customTemplate.put("service.java", "/templates/company/service.java.ftl");
customTemplate.put("serviceImpl.java", "/templates/company/serviceImpl.java.ftl");
customTemplate.put("controller.java", "/templates/company/controller.java.ftl");
// 2. 使用自定义模板
FastAutoGenerator.create(url, username, password)
.templateConfig(builder -> {
builder.entity("/templates/company/entity.java");
builder.mapper("/templates/company/mapper.java");
builder.service("/templates/company/service.java");
builder.serviceImpl("/templates/company/serviceImpl.java");
builder.controller("/templates/company/controller.java");
})
.execute();
}
}
6.2 问题2:需要生成额外的文件
解决方案:通过InjectionConfig添加自定义文件
/**
* 生成额外文件
*/
public class ExtraFileGenerator {
public void generateWithExtraFiles() {
FastAutoGenerator.create(url, username, password)
.injectionConfig(builder -> {
// 添加自定义配置
Map<String, Object> customMap = new HashMap<>();
customMap.put("company", "Spring Boot Tutorial");
customMap.put("author", "System");
customMap.put("version", "1.0.0");
builder.customMap(customMap);
// 添加自定义文件
Map<String, String> customFile = new HashMap<>();
// 生成DTO
customFile.put("DTO.java", "/templates/extra/dto.java.ftl");
// 生成VO
customFile.put("VO.java", "/templates/extra/vo.java.ftl");
// 生成Query
customFile.put("Query.java", "/templates/extra/query.java.ftl");
// 生成Converter
customFile.put("Converter.java", "/templates/extra/converter.java.ftl");
// 生成测试类
customFile.put("Test.java", "/templates/extra/test.java.ftl");
builder.customFile(customFile);
})
.execute();
}
}
6.3 问题3:表字段类型映射不正确
解决方案:自定义类型转换器
/**
* 自定义类型转换器
*/
public class CustomTypeConvertor implements ITypeConvert {
@Override
public IColumnType processTypeConvert(GlobalConfig config,
String fieldType) {
String t = fieldType.toLowerCase();
// 自定义类型映射
if (t.contains("tinyint(1)")) {
return DbColumnType.BOOLEAN;
}
if (t.contains("datetime") || t.contains("timestamp")) {
return DbColumnType.LOCAL_DATE_TIME;
}
if (t.contains("date")) {
return DbColumnType.LOCAL_DATE;
}
if (t.contains("time")) {
return DbColumnType.LOCAL_TIME;
}
if (t.contains("json") || t.contains("text")) {
return DbColumnType.STRING;
}
// 默认处理
return TypeConverts.getTypeConvert(config).processTypeConvert(config, fieldType);
}
}
// 使用自定义类型转换器
FastAutoGenerator.create(url, username, password)
.strategyConfig(builder -> {
builder.entityBuilder()
.convertFileName(entityName -> entityName + "Entity")
.enableLombok()
.idType(IdType.AUTO)
.columnNaming(NamingStrategy.underline_to_camel)
.naming(NamingStrategy.underline_to_camel)
.addTableFills(
new Column("create_time", FieldFill.INSERT),
new Column("update_time", FieldFill.INSERT_UPDATE)
)
.logicDeleteColumnName("deleted")
.versionColumnName("version")
.formatFileName("%s")
// 使用自定义类型转换器
.typeConvert(new CustomTypeConvertor());
})
.execute();
七、进阶优化
7.1 性能优化
/**
* 批量代码生成器 - 优化性能
*/
public class BatchCodeGenerator {
/**
* 批量生成代码,优化性能
*/
public void batchGenerateWithPerformance(String... tableNames) {
long startTime = System.currentTimeMillis();
// 1. 预编译配置
GlobalConfig.Builder globalConfig = new GlobalConfig.Builder()
.author("System")
.outputDir(PROJECT_PATH + "/src/main/java")
.enableSwagger()
.dateType(DateType.TIME_PACK)
.commentDate("yyyy-MM-dd")
.disableOpenDir();
// 2. 批量处理
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
List<CompletableFuture<Void>> futures = Arrays.stream(tableNames)
.map(tableName -> CompletableFuture.runAsync(() -> {
generateSingleTable(tableName, globalConfig);
}, executor))
.collect(Collectors.toList());
// 3. 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long endTime = System.currentTimeMillis();
log.info("批量代码生成完成,耗时: {}ms", endTime - startTime);
executor.shutdown();
})
.join();
}
private void generateSingleTable(String tableName,
GlobalConfig.Builder globalConfig) {
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> globalConfig)
.packageConfig(builder -> {
builder.parent("com.example.demo")
.entity("entity." + tableName.toLowerCase())
.mapper("mapper." + tableName.toLowerCase())
.service("service." + tableName.toLowerCase())
.serviceImpl("service.impl." + tableName.toLowerCase())
.controller("controller." + tableName.toLowerCase());
})
.strategyConfig(builder -> {
builder.addInclude(tableName)
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
.controllerBuilder()
.enableRestStyle();
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
7.2 缓存优化
/**
* 带缓存的代码生成器
*/
@Component
@Slf4j
public class CachedCodeGenerator {
private final Map<String, String> templateCache = new ConcurrentHashMap<>();
private final Map<String, TableInfo> tableInfoCache = new ConcurrentHashMap<>();
/**
* 带缓存的代码生成
*/
public void generateWithCache(String... tableNames) {
// 1. 加载模板到缓存
loadTemplatesToCache();
// 2. 批量生成
Arrays.stream(tableNames).parallel().forEach(tableName -> {
try {
// 检查缓存
if (tableInfoCache.containsKey(tableName)) {
log.info("使用缓存的表信息: {}", tableName);
generateFromCache(tableName);
} else {
// 从数据库加载
TableInfo tableInfo = loadTableInfoFromDB(tableName);
tableInfoCache.put(tableName, tableInfo);
generateTable(tableName, tableInfo);
}
} catch (Exception e) {
log.error("生成表 {} 失败: {}", tableName, e.getMessage(), e);
}
});
}
private void loadTemplatesToCache() {
String[] templates = {
"entity.java.ftl", "mapper.java.ftl",
"service.java.ftl", "serviceImpl.java.ftl",
"controller.java.ftl", "mapper.xml.ftl"
};
Arrays.stream(templates).parallel().forEach(template -> {
try {
String content = loadTemplateContent(template);
templateCache.put(template, content);
log.debug("已缓存模板: {}", template);
} catch (Exception e) {
log.error("加载模板 {} 失败: {}", template, e.getMessage());
}
});
}
}
7.3 监控与告警
/**
* 带监控的代码生成器
*/
@Component
@Slf4j
public class MonitoredCodeGenerator {
private final MeterRegistry meterRegistry;
private final Counter successCounter;
private final Counter errorCounter;
private final Timer generationTimer;
public MonitoredCodeGenerator(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.successCounter = Counter.builder("code.generator.success")
.description("代码生成成功次数")
.register(meterRegistry);
this.errorCounter = Counter.builder("code.generator.error")
.description("代码生成失败次数")
.register(meterRegistry);
this.generationTimer = Timer.builder("code.generator.duration")
.description("代码生成耗时")
.register(meterRegistry);
}
/**
* 带监控的代码生成
*/
public void generateWithMonitor(String... tableNames) {
generationTimer.record(() -> {
try {
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author("System")
.outputDir(PROJECT_PATH + "/src/main/java")
.enableSwagger()
.commentDate("yyyy-MM-dd HH:mm:ss");
})
.packageConfig(builder -> {
builder.parent("com.example.demo")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.controller("controller");
})
.strategyConfig(builder -> {
builder.addInclude(tableNames)
.addTablePrefix("t_")
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
.controllerBuilder()
.enableRestStyle();
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
// 记录成功
successCounter.increment();
log.info("代码生成成功,表: {}", Arrays.toString(tableNames));
} catch (Exception e) {
// 记录失败
errorCounter.increment();
log.error("代码生成失败: {}", e.getMessage(), e);
throw new RuntimeException("代码生成失败", e);
}
});
}
}
八、总结
8.1 最佳实践总结
- 版本管理:始终使用固定的版本组合
- 模板定制:根据公司规范定制模板
- 代码审查:生成后需要人工审查
- 增量生成:只生成需要的文件
- 监控告警:监控生成过程,及时发现问题
8.2 完整配置文件示例
# application-codegen.yml
code:
generator:
# 基础配置
author: 示例作者
output-dir: src/main/java
package-name: com.example.demo
module-name:
# 策略配置
table-prefix: t_,sys_
field-prefix: is_,has_
lombok: true
swagger: true
rest-controller: true
# 模板配置
template-path: classpath:/templates/custom/
overwrite: true
# 数据库配置
datasource:
url: ${spring.datasource.url}
username: ${spring.datasource.username}
password: ${spring.datasource.password}
driver-class-name: com.mysql.cj.jdbc.Driver
# 监控配置
monitor:
enabled: true
metrics-prefix: code.generator
8.3 一键生成脚本
#!/bin/bash
# generate-code.sh
# 设置环境变量
export SPRING_PROFILES_ACTIVE=codegen
# 运行代码生成器
echo "开始生成代码..."
# 方式1:通过测试类生成
mvn test -Dtest=CodeGeneratorTest#testGenerateCode
# 方式2:通过main方法生成
# mvn spring-boot:run -Dspring-boot.run.arguments=--code.generate=true
# 方式3:通过HTTP接口生成
# curl -X POST http://localhost:8080/api/code/generate \
# -H "Content-Type: application/json" \
# -d '{"tables":["t_user","t_role"]}'
echo "代码生成完成!"
8.4 生成的代码结构
src/main/java/com/example/demo/
├── entity/ # 实体类
│ ├── User.java
│ └── Role.java
├── mapper/ # Mapper接口
│ ├── UserMapper.java
│ └── RoleMapper.java
├── service/ # Service接口
│ ├── UserService.java
│ └── RoleService.java
├── service/impl/ # Service实现
│ ├── UserServiceImpl.java
│ └── RoleServiceImpl.java
├── controller/ # Controller
│ ├── UserController.java
│ └── RoleController.java
├── dto/ # DTO
│ ├── UserDTO.java
│ └── RoleDTO.java
├── vo/ # VO
│ ├── UserVO.java
│ └── RoleVO.java
├── query/ # 查询对象
│ ├── UserQuery.java
│ └── RoleQuery.java
├── converter/ # 转换器
│ ├── UserConverter.java
│ └── RoleConverter.java
└── config/ # 配置类
└── MyBatisPlusConfig.java
src/main/resources/
├── mapper/ # XML映射文件
│ ├── UserMapper.xml
│ └── RoleMapper.xml
├── templates/ # 模板文件
│ ├── entity.java.ftl
│ ├── mapper.java.ftl
│ ├── service.java.ftl
│ ├── serviceImpl.java.ftl
│ ├── controller.java.ftl
│ ├── mapper.xml.ftl
│ ├── dto.java.ftl
│ ├── vo.java.ftl
│ └── query.java.ftl
└── application.yml # 配置文件
更多推荐




所有评论(0)