news 2026/10/3 10:00:02

新版MyBatis-Plus代码生成器FastAutoGenerator实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新版MyBatis-Plus代码生成器FastAutoGenerator实战指南

写Java后端的人,大概都有被CRUD支配过的经历。实体类加一个字段,Controller、Service、Mapper、XML全要跟着动;新表建好,光把那套标准文件补齐就要小半天。我第一次用MyBatis-Plus代码生成器时,只当它是个快速生成类的小工具,后来在项目里用熟了才发现,它的真正价值不在于省几行代码,而是把团队几十个模块的CRUD风格拉齐,让每个人生成的类长得一模一样。最近MyBatis-Plus把代码生成器重写成了FastAutoGenerator,老教程里的AutoGenerator写法已经过时,网上很多资料还停在旧版。这篇文章就结合我在新项目里的实操,把新版代码生成器的配置、批量生成过程和落地时的几个关键经验一次讲完。

1. 新版代码生成器为什么值得升级

1.1 从AutoGenerator到FastAutoGenerator:API变在哪

旧版代码生成器的痛点,用过的人应该都有体会。整个生成流程要分别创建DataSourceConfig、GlobalConfig、PackageConfig、StrategyConfig,然后通过setter方法逐个塞进AutoGenerator里。配置项又多又分散,第一次用的时候不看文档根本记不住要配哪些。而且旧版换到新版,API几乎推倒重来,网上的老教程抄过来直接把入门劝退。

新版FastAutoGenerator是MyBatis-Plus 3.5.1之后引入的入口类,核心变化就是链式调用。数据源直接作为create方法的入参,全局配置、包配置、策略配置各自通过一个builder闭包完成,最后execute()执行。代码结构从“声明多个对象再组装”变成了“一条流水线走完”,配置集中、层次清楚,从阅读到修改都友好得多。

// 旧版写法 AutoGenerator generator = new AutoGenerator(); generator.setDataSource(dataSource); generator.setGlobalConfig(new GlobalConfig().setOutputDir("D:/code")); generator.setPackageInfo(new PackageConfig().setParent("com.demo")); generator.setStrategy(new StrategyConfig().setInclude("user")); generator.execute(); // 新版写法 FastAutoGenerator.create("jdbc:mysql://localhost:3306/demo", "root", "123456") .globalConfig(builder -> builder.outputDir("D:/code")) .packageConfig(builder -> builder.parent("com.demo")) .strategyConfig(builder -> builder.addInclude("user")) .execute();

旧版和新版的对比如下。

对比项旧版 AutoGenerator新版 FastAutoGenerator
入口方式创建多个配置对象再set进总类create方法一条链式流水线
数据源配置单独new DataSourceConfigcreate(url, username, password)直接传入
配置结构分散在多个setter对象中globalConfig/packageConfig/strategyConfig闭包内集中配置
批量表生成用StrategyConfig.setInclude传数组strategyConfig里addInclude一次传多个表名
可读性配置项之间关系不直观生成逻辑从上到下逐个可见

新版并没有增加什么革命性的生成能力,它改的是使用体验。把散落的状态集中起来,把需要记忆的配置位置变成IDE的代码提示,这套设计思路其实值得所有工具类项目借鉴。

1.2 它真正解决的问题:不只是省事

代码生成器的本质,是把标准单表CRUD这个过程工业化。对于一张业务表,实体、Mapper、Service、ServiceImpl、Controller、XML,八成内容都是强模式:表名映射、主键策略、字段注解、分页查询、保存、更新、删除。这些代码人工写不难,但很容易反复,更危险的是容易漏。漏加@TableName、漏改Mapper XML里的resultMap、Controller分页参数写错,都是我在实际项目中踩过的坑。生成器把这些重复劳动全部机械化,出错概率降到最低。

容易被忽略的是,生成器还能当团队的代码规范执行器。之前带过一个项目组,五个同事写Controller风格各不相同:有人用@RestController,有人用@Controller,有人返回实体类,有人返回Map。后来把生成器主类统一配好,默认生成REST风格接口和标准分层代码,新同学来了照着生成结果写,风格就不会跑偏。这比贴一页规范文档管用得多。

当然也要说清楚边界。多表关联查询、复杂业务事务、审批流、状态机,这些生成器统统搞不定。它擅长的是单表标准CRUD骨架,生成完之后的业务方法还得自己补。把它当脚手架,别当万能引擎,心态就对了。

2. 生成之前要想清楚的三个配置

2.1 数据源配置是生成器的唯一入口

代码生成器的原理,是连接数据库后读取表结构信息,再根据表结构生成Java代码。所以数据源配置是硬前提,必须保证能连上目标库、能读取到目标表。

我常用的MySQL连接串是这样的:

jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true

几个关键参数要说一下。characterEncoding=utf8是为了避免生成注释时出现中文乱码;serverTimezone必须指定,否则高版本MySQL驱动会报时区错误;useSSL=false和allowPublicKeyRetrieval=true是MySQL 8.x的常见配置,不加allowPublicKeyRetrieval容易遇到“Public Key Retrieval is not allowed”的报错。

注意:生成器只读取表结构,不会修改数据库数据,所以不用担心误操作。直接用项目所用的账号即可,但建议有SELECT权限的账号就够用,最小化权限是实操中的好习惯。

驱动依赖别漏。MySQL 8及以上版本要用com.mysql.cj.jdbc.Driver,项目的JDBC连接下不需要显式设置driverClassName,FastAutoGenerator能根据URL自动识别,但依赖必须引入。

2.2 全局配置和包配置决定代码落在哪

初次使用的人最容易栽在输出目录上。FastAutoGenerator的globalConfig里如果不显式设置outputDir,不同版本对默认输出目录的处理并不一致,有些会落到系统临时目录下,跑完之后满世界找不到生成的文件。所以我建议一律使用绝对路径,并且是项目内的明确目录。

.globalConfig(builder -> builder .outputDir("D:/workspace/mall/mall-generator/src/main/java") .author("your-name") .enableSwagger() .enableFileOverride())

outputDir表示生成Java代码的根目录,注意是src/main/java,不是项目根目录。如果生成在项目根目录下,文件放错位置,Maven编译根本不认识。packageConfig负责生成文件的包路径结构:

.packageConfig(builder -> builder .parent("com.example.mall") .moduleName("system") .entity("entity") .mapper("mapper") .service("service") .serviceImpl("service.impl") .controller("controller") .pathInfo(Collections.singletonMap(OutputFile.xml, "D:/workspace/mall/mall-generator/src/main/resources/mapper")))

parent加上moduleName是基础包名。上面这段配置生成出来的实体路径就是com.example.mall.system.entity,Service实现路径是com.example.mall.system.service.impl。这一层命名一定要想好再定,因为生成之后再改包名很痛苦。Mapper的XML和Java代码不在同一个目录,通过pathInfo单独指定到src/main/resources/mapper下。如果这里忘了配,XML文件就可能生成到java目录里,启动时扫不到,后面排查非常麻烦。

2.3 策略配置决定哪些表生成、以什么形态生成

策略配置是生成器最灵活的地方,它决定三件事:生成哪些表、表名怎么处理、字段注解如何映射。

最核心的addInclude用来指定要生成的表名。想批量生成,直接一次性传入多个表名:

.strategyConfig(builder -> builder .addInclude("t_user", "t_order", "t_goods") .addTablePrefix("t_"))

addTablePrefix表示把表名前缀去掉。表名t_user生成实体类时只保留User,避免实体类名变成别扭的TUser。前缀处理还有其他选项,比如addTableSuffix、字段前缀,但按我的经验最常用的还是表前缀。

实体的生成形态也是在策略里控制的。想让实体类使用Lombok注解而不是满屏getter/setter,可以这样写:

.strategyConfig(builder -> builder .addInclude("t_user") .addTablePrefix("t_") .entityBuilder() .enableLombok() .enableTableFieldAnnotation() .logicDeleteColumnName("deleted") .versionColumnName("version") .controllerBuilder() .enableRestStyle())

这段配置里几个关键项的效果分别是:enableLombok让实体生成@Data注解,不再生成冗长的getter/setter;enableTableFieldAnnotation让实体字段标注@TableField,表字段和Java属性严格对应;logicDeleteColumnName和versionColumnName指定逻辑删除字段与乐观锁字段,方便后续业务直接使用这些能力;enableRestStyle让Controller生成@RestController而不是@Controller,同时方法返回结果也更现代化。定期生成的时候,把这些开关一次性定好,后面所有表都按同一套规则生成。

3. 实战:一次生成用户、订单、商品三个模块

3.1 依赖怎么加

我这次用的是Spring Boot项目,MyBatis-Plus版本是3.5.x。pom里需要引入mybatis-plus-boot-starter、mybatis-plus-generator以及模板引擎依赖。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.32</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

这里有两个注意点。第一,mybatis-plus-generator的版本尽量和mybatis-plus-boot-starter保持一致,否则可能出现不兼容的API调用。第二,FastAutoGenerator默认使用的模板引擎是Velocity,如果你像我一样用Freemarker,就必须显式引入freemarker依赖,并在生成器代码里指定templateEngine(new FreemarkerTemplateEngine())。依赖缺失时运行会直接报类找不到,这个坑很常见。

3.2 生成器主类完整写法

生成器做成了一个独立的main方法,放在项目单独的tools包下,和业务代码隔离。完整代码如下:

import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.Collections; public class MyBatisPlusGenerator { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; String username = "root"; String password = "123456"; FastAutoGenerator.create(url, username, password) .globalConfig(builder -> builder .outputDir("D:/workspace/mall/mall-generator/src/main/java") .author("your-name") .enableSwagger() .enableFileOverride()) .packageConfig(builder -> builder .parent("com.example.mall") .moduleName("system") .entity("entity") .mapper("mapper") .service("service") .serviceImpl("service.impl") .controller("controller") .pathInfo(Collections.singletonMap(OutputFile.xml, "D:/workspace/mall/mall-generator/src/main/resources/mapper"))) .strategyConfig(builder -> builder .addInclude("t_user", "t_order", "t_goods") .addTablePrefix("t_")) .templateEngine(new FreemarkerTemplateEngine()) .execute(); } }

enableFileOverride这个配置值得单独拎出来说。FastAutoGenerator默认不覆盖已经存在的文件,也就是说同一个表如果生成过一次,再次执行时不会重新生成,改了表结构也看不到变化。如果希望每次生成都强制覆盖,必须显式开启这个开关。我第一次用新版生成器时没开它,改完表字段再跑生成,发现实体类还是老样子,排查了半天才发现是这个默认行为。

3.3 跑起来之后能看到什么

执行main方法后,控制台会打印SQL执行日志和生成过程。只要数据源能连通、表名写对,几秒之内就能生成完毕。生成后的目录结构是这样:

mall-generator/src/main/java/com/example/mall/system/ ├── controller │ ├── TOrderController.java │ ├── TGoodsController.java │ └── TUserController.java ├── entity │ ├── TOrder.java │ ├── TGoods.java │ └── TUser.java ├── mapper │ ├── TOrderMapper.java │ ├── TGoodsMapper.java │ └── TUserMapper.java ├── service │ ├── ITOrderService.java │ ├── ITGoodsService.java │ ├── ITUserService.java │ └── impl │ ├── TOrderServiceImpl.java │ ├── TGoodsServiceImpl.java │ └── TUserServiceImpl.java └── resources/mapper ├── TOrderMapper.xml ├── TGoodsMapper.xml └── TUserMapper.xml

批量生成最大的好处就是一次把所有相关文件生成齐全,不用逐个表跑。三张表对应3个实体、3个Mapper、3个Service、3个Controller,加起来十多个文件,几秒钟搞定。相比之下手工写不是写不完,而是每张表都要重复同一套模板式操作,时间成本高且容易审美疲劳。

3.4 打开生成的文件检查

生成完先别直接抄进项目,逐个文件扫一遍很重要。实体类是基础,长这样:

@TableName("t_user") public class TUser { @TableId(value = "id", type = IdType.AUTO) private Long id; @TableField("username") private String username; @TableField("age") private Integer age; private String email; }

这里能看到几个关键点:表名映射正确去掉了前缀,主键字段标注了自增策略,非主键字段通过@TableField与数据库字段对应。Service接口继承了MyBatis-Plus的IService,ServiceImpl继承了ServiceImpl,这意味着基础CRUD方法全部内置,不需要自己实现。

Controller默认生成了一组标准接口,包含分页列表、详情、保存、更新、删除。分页列表方法的写法类似:

@GetMapping("/page") public IPage<TUser> page(@RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size) { Page<TUser> page = new Page<>(current, size); return userService.page(page); }

到这里,一套可以直接启动的CRUD骨架就就位了。不过骨架归骨架,真落到业务里还有几件事必须改造,下面展开讲。

4. 生成代码别急着用:落地改造的三个重点

4.1 公共字段自动填充,省掉一半重复代码

业务表里最常见的两个公共字段是create_time和update_time。如果每次保存、更新都手动set,不仅代码丑,还容易漏。MyBatis-Plus提供了MetaObjectHandler机制,可以自动填充这两个字段。

定义一个handler类:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

实体类中对应字段加上自动填充策略:

@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;

配置完成后,以后新增、更新操作完全不用再管这两个字段,框架自动写入时间。为了减少后续补这步的麻烦,可以在生成器策略里通过FieldFill接口给公共字段设置填充策略,但更稳妥的做法是先生成,再统一在一个地方补充handler和注解。因为自动填充属于通用逻辑,放在公共基类里比每张表单独处理更优雅。

4.2 无状态通用CRUD服务怎么设计

生成器生成的Service继承了IService,里面已经带了save、saveBatch、removeById、listByIds、page等方法。很多项目到这里就直接用了,但实际业务里除了Controller层,定时任务、消息队列消费者、命令行脚本也经常要做增删改查。如果每个地方都注入实体对应的Service,代码重复度还是高。更好的做法是抽一层无状态的通用CRUD服务。

无状态的意思是:服务实例不持有业务数据,不在成员变量里缓存查询结果,不保存会话上下文,所有方法都是传入参数、返回结果、调用结束立即释放。这样同一个服务可以被多个业务场景安全复用,不会被上一次调用的残留数据干扰。可以理解为档案室的抽屉,你报编号我取档案,取完就关抽屉,不会因为你上次看过什么而影响这次的结果。

通用CRUD工具类的设计可以这样:

@Service public class CommonCrudService<T> { @Autowired private BaseMapper<T> baseMapper; public T selectById(Serializable id) { return baseMapper.selectById(id); } public List<T> selectBatchIds(Collection<? extends Serializable> idList) { return baseMapper.selectBatchIds(idList); } public boolean insert(T entity) { return baseMapper.insert(entity) > 0; } public boolean insertBatch(Collection<T> entityList) { entityList.forEach(baseMapper::insert); return true; } public boolean updateById(T entity) { return baseMapper.updateById(entity) > 0; } public boolean deleteById(Serializable id) { return baseMapper.deleteById(id) > 0; } public List<T> list(Wrapper<T> queryWrapper) { return baseMapper.selectList(queryWrapper); } }

这个类的核心价值在于直接操作BaseMapper,不依赖具体业务Service。方法全部是纯函数式的:输入参数、输出结果、内部无状态。批量插入的时候要注意,如果list非常大,一次性遍历插入数据库连接压力大,建议按每批200到500条切割循环处理:

public void insertBatchLarge(Collection<T> entityList) { List<T> list = new ArrayList<>(entityList); int batchSize = 500; for (int i = 0; i < list.size(); i += batchSize) { List<T> subList = list.subList(i, Math.min(i + batchSize, list.size())); subList.forEach(baseMapper::insert); } }

当然,如果数据量巨大,直接用ServiceImpl自带的saveBatch配合分段提交更高效。MyBatis-Plus的saveBatch方法默认batchSize是1000,在批量操作时是很实用的内置能力。

4.3 Controller返回结构统一

生成器生成的Controller直接返回实体对象或IPage,这在内部系统够用,但对外提供API时,统一返回结构迟早要做。常见的做法是定义一个BaseResult或Result类,包装code、message、data三个字段,然后把Controller里的返回类型改成Result。手动改几十个Controller确实麻烦,有精力的团队可以直接改代码生成器的模板文件,把Controller模板里的返回类型统一替换成Result包装。这也是代码生成器灵活性的体现:模板不是写死的,Freemarker模板文件可以按团队规范调整。

5. 常见问题与排查技巧实录

5.1 生成完找不到文件

最常见的原因是outputDir没设置或用了相对路径。我见过不止一个同事跑完生成器,在项目目录里翻半天找不到产物,最后发现文件生成到了系统的临时目录。这个问题没有太多技术含量,但特别消磨心情。解决办法就是按前面写的,outputDir一律用绝对路径,指定到项目的src/main/java目录。生成之前确认路径存在,否则部分版本不会主动创建目录也会导致异常。

5.2 重复生成不生效

表结构调整之后重新跑生成器,发现实体没变化。先把globalConfig里的enableFileOverride开了再说。FastAutoGenerator的默认行为是跳过已存在的文件,这个设计是为了防止误覆盖,但开发阶段如果需要反复调整,不开这个开关就会觉得生成器“没反应”。改表结构前先确认这个开关是开的,能省不少时间。

5.3 MySQL连接相关报错

运行生成器时遇到“Public Key Retrieval is not allowed”,是MySQL 8.x的常规问题,连接串加上allowPublicKeyRetrieval=true&useSSL=false即可。遇到“Could not create connection to database server”,优先检查时区参数serverTimezone是否配置。遇到“Table not found”之类的错误,检查表名是否写错,注意大小写敏感问题。

我整理了一份问题速查表,基本覆盖了日常使用中的高频故障。

症状常见原因处理办法
生成后找不到文件outputDir未设置或用了相对路径显式配置绝对路径到src/main/java
重复生成不更新默认跳过已存在文件globalConfig开启enableFileOverride
Public Key Retrieval is not allowedMySQL 8公钥获取限制URL加allowPublicKeyRetrieval=true&useSSL=false
实体类没有Lombok注解策略中未开启LombokentityBuilder().enableLombok()
Controller是@Controller不是@RestController未开启REST风格controllerBuilder().enableRestStyle()
Mapper XML扫描不到XML路径与配置不匹配项目配置mybatis-plus.mapper-locations=classpath*:mapper/**/*.xml
中文字段注释乱码连接串没有指定编码URL加characterEncoding=utf8

5.4 生产级项目的小建议

版本升级到3.5.x之后,生成器配置和运行基本稳定,但有几个小习惯建议养成分。生成器主类放独立目录,不要放业务包,最好标记为工具类,因为在生产环境打包时不需要跑它。生成完的代码先编译一次再提交,避免把错误文件推进仓库。生成器不需要每次启动项目都跑,它只是开发阶段的一次性工具,建好表后跑一次就够了,以后表结构变更再重新执行。

最后再分享一个我实际项目里的经验。如果团队有多个人都需要用到代码生成器,不要让每个人都配一套生成规则,而是由一个人把生成器主类配置好,commit到仓库里,其他人拉代码后只改数据库密码就能直接用。这样所有人生成出来的代码风格完全一致,code review时也不用再因为个人习惯不同而争论格式问题了。

我个人最开始接触代码生成器时也觉得可有可无,但用习惯了之后,它的收益体现在长期维护上。项目里表越多、模块越碎片化,生成器带来的节省越明显。新表进来改一下addInclude的表名列表,跑一下main方法,一套标准代码直接落盘,剩下的时间全部留给业务逻辑。这种投入产出比,值得每个用MyBatis-Plus的Java后端同学花上一下午把这套流程搭起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 9:58:42

LAN8720硬件设计避坑指南:晶振、PHY地址、复位时序等关键细节

前阵子帮朋友调一块带网口的板子&#xff0c;主控是STM32F407&#xff0c;PHY用的就是LAN8720。板子第一版流片回来&#xff0c;现象很典型&#xff1a;上电后Link指示灯偶尔亮&#xff0c;MDIO读PHY寄存器时好时坏&#xff0c;ping包有一定概率丢&#xff0c;甚至有时候干脆协…

作者头像 李华
网站建设 2026/10/3 9:58:37

STEPQuant:面向循环神经网络的状态感知量化方法

1. 这不是普通量化&#xff1a;STEPQuant直击循环状态量化的“时间敏感性”痛点 你有没有遇到过这样的情况&#xff1a;模型在训练时一切正常&#xff0c;精度达标&#xff0c;但一旦做后训练量化&#xff08;Post-Training Quantization, PTQ&#xff09;&#xff0c;尤其是对…

作者头像 李华
网站建设 2026/10/3 9:58:06

零基础15分钟搞定Claude桌面版:安装配置与避坑指南

1. 为什么我劝你先搞清楚Claude桌面版到底是个什么东西 很多人第一次听到“Claude桌面版”&#xff0c;脑子里冒出来的画面是又一个套壳聊天窗口&#xff0c;跟网页版没什么区别。我一开始也这么想&#xff0c;直到连续几天在浏览器里来回切标签页、复制粘贴长文档、被会话超时…

作者头像 李华
网站建设 2026/10/3 9:57:52

C++构造函数初始化列表:底层原理、必用场景与性能优化

1. 先从一次代码评审说起 前阵子做代码评审&#xff0c;看到同事新写的类里有个成员是 const int &#xff0c;构造函数里直接 m_value 100; 这样赋值&#xff0c;编译死活过不去。他一脸困惑&#xff1a;"构造函数里不就能初始化成员吗&#xff0c;为啥 const 的不行…

作者头像 李华
网站建设 2026/10/3 9:57:31

基于Matlab的凸轮设计与仿真:绘制多款凸轮轮廓曲线

做机械设计、参加工训赛或者搞自动化产线的人&#xff0c;应该都有过被凸轮支配的恐惧。基圆半径、压力角、滚子半径、偏距&#xff0c;再加上推程、回程、远休止、近休止&#xff0c;手工画轮廓又慢又容易错&#xff0c;改一个参数就得全部重来。我最近把整套流程搬到 Matlab …

作者头像 李华
网站建设 2026/10/3 9:57:30

OpenShell:多项目多机器终端环境管理实战指南

先交代一下背景。我日常要同时面对三台机器&#xff1a;办公室的 Windows 工作站、家里那台常年跑着 Ubuntu 的笔记本&#xff0c;以及几台远程 Linux 服务器。听起来不算极端&#xff0c;但真正折腾人的不是设备数量&#xff0c;而是"终端环境"这件事——每台机器上…

作者头像 李华