假设你现在正在负责某个系统的开发工作,按业务功能至少设计了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 最佳实践总结

  1. 版本管理:始终使用固定的版本组合
  2. 模板定制:根据公司规范定制模板
  3. 代码审查:生成后需要人工审查
  4. 增量生成:只生成需要的文件
  5. 监控告警:监控生成过程,及时发现问题

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        # 配置文件
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐