☰
MyBatis-Plus代码生成器实战:SpringBoot集成FastAutoGenerator详解
2026/10/5 1:04:40 网站建设 项目流程

1. 为什么团队里总有人还在手写Mapper?先聊聊代码生成这件事

先说个我观察了很久的现象:大多数Java后端项目里,真正拖慢开发进度的往往不是业务逻辑本身,而是那些结构高度雷同的基础代码。一个标准的数据访问层,无非就是实体类、Mapper接口、XML文件或者Service层的CRUD模板,这些代码几乎没有任何智力含量,但每个新模块都要重写一遍。我见过不少项目组,团队成员每天花两三个小时在复制粘贴上,再把包名、类名、字段类型逐个改掉,效率低不说,还特别容易漏改。

MyBatis-Plus的自动代码生成器解决的就是这个问题。它可以根据数据库表结构,一键生成Entity、Mapper、Service、Controller这一整套基础代码,而且生成的代码直接贴合MyBatis-Plus的API风格,不用自己再去手写那些BaseMapper的实现。说白了,它就是把你从"机械劳动"里解放出来,把时间留给真正需要动脑的业务设计。

这次我打算用一套完整的实操流程来演示怎么在SpringBoot项目里把它跑起来。适合谁看呢?如果你刚接触MyBatis-Plus,想在项目里引入代码生成但不知道从哪下手;或者你已经用了MyBatis-Plus但一直是手写基础代码,想提升效率;再或者你带团队,想统一团队的数据访问层代码规范——这篇文章应该都能给你一些参考。

需要注意的是,网上关于这个生成器的教程版本差异非常大,老版本用AutoGenerator,新版本迁到了FastAutoGenerator,API变化不小。我这次直接基于目前主流的新版本写法,把关键配置一点一点拆开讲清楚。

2. 在开始之前:版本选型和环境准备,别在这上面翻车

2.1 SpringBoot与MyBatis-Plus的版本兼容矩阵

版本问题是我最想先说的。很多人照着老教程写代码,发现类都找不到,十有八九是版本不匹配。MyBatis-Plus从3.5.0版本开始,代码生成器从AutoGenerator迁移为FastAutoGenerator,同时拆分出了独立的mybatis-plus-generator模块,和核心包分开维护。这里有一个重要的前置条件:3.5.0之后的生成器默认需要Java 8以上,而SpringBoot 3.x则强制要求Java 17。

我把实际验证过的组合整理在下面:

SpringBoot版本JDK版本MyBatis-Plus版本代码生成器版本备注
2.7.x1.83.5.3以上3.5.3以上经典组合,资料最多,最稳
2.7.x1.83.5.13.5.1老项目常用,兼容性好
3.0.x-3.1.x173.5.3以上3.5.3以上注意javax改jakarta
3.2.x173.5.5以上3.5.5以上新版推荐,支持JDK17特性

如果用SpringBoot 3.x,还需要注意一个坑:javax.servlet已经迁移为jakarta.servlet,代码生成器模板里如果有相关的引用需要对应调整。另外SpringBoot 3.x对mybatis-plus的依赖也要用最新的3.5.5+,老版本在SpringBoot 3下会出现MybatisPlusInterceptor无法注入的问题。

2.2 Maven依赖的最简配置方案

无论你选哪套版本组合,核心依赖就两个:mybatis-plus-boot-starter和mybatis-plus-generator。另外还需要一个模板引擎,生成器默认支持Velocity,你也可以换成Freemarker或Beetl。我用的是Velocity,它最省事,不需要额外引入依赖。

如果你是SpringBoot 2.7.x,直接在pom.xml里加这些:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> </dependency>

SpringBoot 3.x就把版本换到3.5.5以上,starter不变。注意很多教程还会让你加velocity-engine-core依赖,但3.5.3之后的生成器内置了Velocity引擎,不需要额外加。加了也不报错,但属于多余的依赖。

数据库驱动肯定要有的,mysql-connector-j或者mysql-connector-java,根据你本地的MySQL版本选。这里有个小提醒:如果你连接的是MySQL 8以上,驱动类名是com.mysql.cj.jdbc.Driver,老的com.mysql.jdbc.Driver已经废弃了,生成器连库失败时优先检查这个地方。

2.3 建库建表:生成器的输入是表结构

开始配置之前,先准备一张测试表。代码生成器的核心输入就是数据库表,表结构设计得规范不规范,直接影响生成出来的实体类质量。我用一张比较典型的业务表来做演示:

CREATE DATABASE IF NOT EXISTS demo_generator DEFAULT CHARACTER SET utf8mb4; USE demo_generator; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `status` tinyint(1) DEFAULT '1' COMMENT '状态:1启用,0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='系统用户表';

这张表覆盖了比较常见的字段类型:主键、字符串、整数、时间、逻辑删除标记。数据库的字段注释一定要写,因为生成器默认会读取注释作为实体类字段的@ApiModelProperty或者Javadoc注释,注释写清楚了,生成的代码可读性直接上一个档次。

3. 核心实操:基于FastAutoGenerator从零跑通一次生成

3.1 生成器配置的骨架结构

MyBatis-Plus 3.5.x的生成器核心入口是FastAutoGenerator,它采用链式调用的风格,整个配置过程大致分成四段:数据源配置、全局配置、包配置、策略配置。下面这段代码是我在实际项目里用的基础版本,先整体过一遍,再逐个参数拆解。

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.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; import java.util.Collections; public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create( "jdbc:mysql://localhost:3306/demo_generator?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai", "root", "your_password" ) // 全局配置 .globalConfig(builder -> { builder.author("YourName") // 作者名,会出现在类注释上 .outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出目录 .dateType(DateType.TIME_PACK) // 日期类型策略 .commentDate("yyyy-MM-dd"); // 注释日期格式 }) // 包配置 .packageConfig(builder -> { builder.parent("com.example.demo") // 父包名 .moduleName("system") // 模块名,可留空 .entity("entity") // 实体类包名 .mapper("mapper") // Mapper接口包名 .service("service") // Service接口包名 .serviceImpl("service.impl") // Service实现类包名 .controller("controller") // Controller包名 .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper")); }) // 策略配置 .strategyConfig(builder -> { builder.addInclude("sys_user") // 要生成的表名 .addTablePrefix("sys_") // 表前缀,生成时会去掉 .entityBuilder() .enableLombok() // 启用Lombok .enableTableFieldAnnotation() // 字段上加@TableField注解 .logicDeleteColumnName("deleted") // 逻辑删除字段 .controllerBuilder() .enableRestStyle() // 生成@RestController .serviceBuilder() .formatServiceFileName("%sService") .formatServiceImplFileName("%sServiceImpl"); }) .templateEngine(new VelocityTemplateEngine()) .execute(); } }

不要直接把这个类扔在业务代码里,我习惯单独放在src/test/java的某个工具包下,或者建一个独立的generator目录。因为生成器只需要跑一次,跑完就没用了,没必要污染主代码目录。

3.2 全局配置里真正影响代码质量的几个参数

globalConfig这部分看起来简单,但有几个参数直接决定生成代码是否符合你的项目规范,而且是网上的老教程经常忽略的。

第一个是dateType。它控制实体类里日期字段的Java类型映射。可选值有DateType.ONLY_DATE(映射为java.util.Date)、DateType.SQL_PACK(映射为java.sql.Date)、DateType.TIME_PACK(映射为java.time.LocalDateTime)。我在2023年之后新建的项目基本都用TIME_PACK,因为LocalDateTime对时间处理更友好,配合Jackson的@JsonFormat可以很方便地控制序列化格式。如果你的项目还在用Date,兼容老代码,那就保持默认的ONLY_DATE。

第二个是outputDir。建议用System.getProperty("user.dir") + "/src/main/java"这种动态拼法,不要硬编码成"D:/code/..."。原因很简单:项目换台电脑或者别人clone下来,硬编码路径直接就废了。user.dir在Maven项目的执行目录下就是当前模块的根目录,拼上相对路径是通用的做法。

第三个容易被忽略的是commentDate。它只影响生成代码上方注释里的日期格式,默认是MM/dd HH:mm,我个人觉得不带年份的格式不太利于追溯,改成yyyy-MM-dd更清楚。

3.3 包配置:决定代码落盘位置的导航系统

packageConfig这里是最多人搞混的地方。parent、moduleName、entity这三者拼起来才是实体类的最终包路径。比如parent("com.example.demo")、moduleName("system")、entity("entity"),最终生成的实体类就是com.example.demo.system.entity.SysUser。

这里有个设计倾向:如果项目是单模块小项目,moduleName可以留空,让所有代码都在parent下按entity、mapper分层;如果项目按业务域拆模块,那么moduleName可以起到子模块分包的作用,system就是其中一个业务域。

pathInfo也是个关键配置。它专门指定XML文件落盘位置。如果你不配置这一项,XML文件会生成到Java包目录里,也就是src/main/java下面,这不符合项目的常规结构,Maven打包时也不会把它当作资源文件处理。配置成src/main/resources/mapper就对了。这个细节在官方文档里写得不显眼,但我见过很多新手在这个地方翻车,生成的Mapper XML不被识别,运行时疯狂报Invalid bound statement (not found)。

3.4 策略配置:批量生成多表时的筛选逻辑

strategyConfig是控制生成范围的。最简单的情况就是addInclude("sys_user")只生成一张表。如果一张张加麻烦,addInclude支持传多个参数:addInclude("sys_user", "sys_role", "sys_menu")。

反向的场景是:表特别多,大部分都要生成,少数不想生成。这时候可以用addExclude("sys_log")排除掉个别表。还有两个进阶的匹配方式:addTablePrefix用于去掉表前缀,addTableSuffix用于去掉表后缀。

比如表名叫sys_user,配置了addTablePrefix("sys_"),生成出来的实体类就是User而不是SysUser。表名前缀这个东西,业界有争议。我个人的习惯是保留前缀对应的语义,因为很多项目里裸的User可能和某个第三方包的类重名,带上Sys反而更安全。这个看团队约定,生成器只是提供这个能力。

还要注意,addTablePrefix("sys_")只影响表名前缀的剥离,不影响数据库字段。字段前缀需要用addFieldPrefix单独配置,这个后面讲字段策略时再展开。

3.5 实体类生成:Lombok、字段注解与逻辑删除的配合

实体类是生成器所有产物里最需要精细配置的一块。entityBuilder()下面有一堆开关,我挑几个重点讲。

enableLombok()是最值得开启的选项。开启后生成的实体类上会有@Data注解,不再生成一堆getter/setter。很多公司规范里都推荐用Lombok,前提是项目里已经引入了Lombok依赖。如果你们团队不允许用Lombok,不开启就是,生成器会退回传统的getter/setter写法。

enableTableFieldAnnotation()我强烈建议开启。开启后,每个实体字段上都会加@TableField("xxx")注解。这样做的价值是:当数据库字段名和Java驼峰命名的映射出现偏差时,MyBatis-Plus不再依赖map-underscore-to-camel-case这个全局开关来猜测,映射关系变得显式可靠。尤其在字段很多、命名不规范的表上,这个特性能在运行时节省不少排查时间。

逻辑删除字段的配置容易被忽略。如果你的表里有deleted这种逻辑删除标记,需要在策略里指定:

.logicDeleteColumnName("deleted")

这句话生成的效果是:实体类的deleted字段上会加上@TableLogic注解。有了这个注解,MyBatis-Plus执行deleteById时不会真正删除记录,而是执行update set deleted = 1。这是业务系统里非常实用的能力,但如果你没有配置它,生成器只会把deleted当成一个普通字段,逻辑删除就完全不生效。

3.6 Controller和Service生成的默认风格

controllerBuilder().enableRestStyle()是Controller配置里最核心的开关。开启后生成的Controller类使用@RestController注解,而不是@Controller。在前后端分离的模式下,这也是当前的主流选择。@RestController是@Controller和@ResponseBody的组合,所有方法的返回值直接序列化为JSON,不用每个方法都额外标注。

Service层生成的东西分两部分:接口和实现类。formatServiceFileName("%sService")控制接口命名格式,%s会被替换为实体类名。如果表是sys_user,生成的Entity是User,那么Service接口就是UserService。同理,formatServiceImplFileName("%sServiceImpl")生成UserServiceImpl。

生成的Service接口默认继承IService<User>,实现类默认继承ServiceImpl<UserMapper, User>。这意味着你不需要写任何CRUD方法,就拥有了getById、saveBatch、listByMap这一整套现成能力。这是MyBatis-Plus代码生成器和传统MyBatis Generator最大的区别:它不只是生成CRUD模板,生成的Service天然就嵌入了MyBatis-Plus的能力体系。

3.7 运行与产物验证

配置写好之后,直接运行main方法。控制台会输出类似这样的日志:

========== 开始生成代码 ========== 正在生成 entity... 正在生成 mapper... 正在生成 service... 正在生成 serviceImpl... 正在生成 controller... ========== 代码生成完成 ==========

跑完之后去目录里看,src/main/java下应该会出现一整棵包树,src/main/resources/mapper下出现UserMapper.xml。

这里要特别提醒:生成的代码不要直接无脑用。至少以下三处需要手动检查。第一,拼命生成的UserController里自带了一套增删改查接口,但没有任何权限控制,如果项目有安全框架,要根据实际情况加上@PreAuthorize注解或拦截规则。第二,UserServiceImpl里没有任何业务逻辑,它只是一个空壳,实际业务方法要自己往里写。第三,实体类里如果有@TableField("create_time")这种带下划线的字段,需要确认createTime这个Java属性名符合团队命名规范。

4. 忘了说数据库类型映射?这张表告诉你字段对应关系

很多人生成完代码发现实体类的类型和预期不一致,比如数据库里的tinyint生成了Integer,decimal生成了BigDecimal。其实这些映射关系MyBatis-Plus有固定的规则,一般来说不需要手动干预,但了解规则有助于排查问题。

数据库字段类型生成的Java类型
varchar / charString
int / integerInteger
bigintLong
tinyintInteger
smallintShort
decimal / numericBigDecimal
floatFloat
doubleDouble
boolean / bitBoolean
dateLocalDate(TIME_PACK策略下)
timeLocalTime(TIME_PACK策略下)
datetime / timestampLocalDateTime(TIME_PACK策略下)
blob / longblobbyte[]
text / longtextString

这个表不需要死记,但有个现象值得讲一下。tinyint(1)在MySQL 8里语义比较特殊,很多人以为它会映射成Boolean,但MyBatis-Plus默认映射为Integer。如果你确实希望tinyint(1)映射为Boolean,就需要在生成时通过自定义ITypeConvertHandler来实现,或者干脆在生成后手动改实体类字段类型。

另一个容易踩的坑是unsigned类字段。比如bigint unsigned在一些驱动版本里会被映射为BigInteger,如果你的业务代码统一使用了Long,还要注意数值溢出问题。这类边角问题多和数据建模设计相关,生成器本身不做判断,只能靠人来兜底。

5. 把生成产物整合进SpringBoot:一个能直接启动的最小闭环

代码生成完毕,接下来要把这些文件真正接入SpringBoot项目里,让它跑起来。这一步看着简单,但有个高频错误:启动类或配置类没有扫描到Mapper,导致UserMapper这个Bean无法注入。

5.1 正确引入Mapper扫描

首先确认SpringBoot的启动类上有这样的注解:

package com.example.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.demo.system.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

@MapperScan的值必须是生成代码时配置的包路径。如果你的模块是com.example.demo.system、mapper包名是mapper,那这里就写com.example.demo.system.mapper。还有一种替代方案:给每个Mapper接口加@Mapper注解。但接口多了以后非常啰嗦,我建议用@MapperScan统一管理。

5.2 application.yml的MyBatis-Plus配置

在src/main/resources/application.yml里加上MyBatis-Plus的配置:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo_generator?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

mapper-locations必须配置,否则UserMapper.xml不会被加载。这个配置项的值是classpath*:mapper/**/*.xml,星号不能省。

日志项log-impl: StdOutImpl是开发阶段的利器。配置后,控制台会打印每个SQL的执行语句和参数值。排错时看到Preparing: SELECT ...这种输出,比你自己猜SQL强得多。

map-underscore-to-camel-case这个配置需要注意:如果你在生成策略里已经开启了enableTableFieldAnnotation,这个全局开关的作用就被弱化了,因为注解的优先级更高。但如果生成的实体类里没有@TableField注解的字段恰好是下划线风格,这个开关才能兜底。保险起见,两者都配置上不会冲突。

5.3 写一个测试接口验证全链路

为了确认整套链路OK,我在项目里写一个简单的测试Controller。其实生成的UserController已经自带CRUD接口了,直接启动项目访问就行。

启动项目后,访问http://localhost:8080/user/list,正常会返回一个用户列表。MyBatis-Plus生成的Controller默认有一个list方法,内部调用userService.list(),走的正是SELECT * FROM sys_user WHERE deleted=0这条逻辑删除感知的查询。

我第一次跑通这个流程时,遇到的问题是Whitelabel Error Page,后台日志显示Invalid bound statement (not found): com.example.demo.system.mapper.UserMapper.selectList。原因就是mapper-locations没配对,XML文件没被加载。把classpath*:mapper/**/*.xml加上后,问题立刻消失。所以如果你们跑起来也报这个错,优先检查这个配置。

6. 进阶玩法:自定义模板、Controller风格与多模块项目的适配

基础流程跑通后,很多人会在实际项目中遇到个性化需求。这里讲几个我常用的进阶配置,用来适配不同团队、不同项目的风格差异。

6.1 修改Controller的继承结构

默认生成的Controller是一个独立的类,不继承任何父类。但在统一封装返回结果的项目里,通常希望Controller继承一个BaseController,让所有接口直接返回Result<T>这种统一格式。

在策略配置里可以这样改:

.controllerBuilder() .enableRestStyle() .superClass("com.example.demo.common.BaseController")

这样生成的Controller会继承BaseController。前提是你项目里已经有这个父类。同理Service和ServiceImpl也有superClass配置,但用的场景相对少。

接口返回统一结果,我强烈建议在生成之后自己改一下方法签名,或者在模板层直接定制。MyBatis-Plus自带的Controller方法返回的是Result或者直接是实体对象,对于需要统一响应结构的项目来说不够用。比较务实的做法是:先生成,再按团队规范改,毕竟生成器只是第一步。

6.2 自定义模板引擎

如果你觉得默认生成的代码格式不符合团队规范,最彻底的方式是自定义模板。MyBatis-Plus生成器的模板本身是模板文件,默认在mybatis-plus-generator的jar包里,文件路径大概是/templates/controller.java.vm。

自定义模板的方法:在项目src/main/resources下建立templates目录,把默认模板文件解压出来改好后放进去,然后在代码里指定:

.templateBuilder() .controller("/templates/controller.java.vm") .mapper("/templates/mapper.java.vm")

模板里用的是Velocity语法,$!{table.comment}这类占位符会被替换成实际值。改动前建议先看一遍默认模板,理解每个变量是什么意思,再动手改。我在实际项目中改得最多的是实体类的类注释规范、Controller的返回类型、方法上的Swagger注解格式这三个地方。

6.3 多模块项目的包路径适配

多模块项目的包路径配置与单模块不同。假设一个典型的三模块工程:

parent ├── common ├── system └── business

生成器要输出到system模块,outputDir就不能用System.getProperty("user.dir") + "/src/main/java"了,因为user.dir指向的是parent根目录。正确的做法是:

String projectPath = System.getProperty("user.dir"); builder.outputDir(projectPath + "/system/src/main/java");

或者在system模块下跑生成器,user.dir就自动指向system目录。哪个顺手用哪个,关键是要搞清楚user.dir的真实指向。

XML文件在多模块下也同理,pathInfo里的路径要拼上模块目录:

.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/system/src/main/resources/mapper"));

6.4 批量生成时如何避免覆盖已有代码

生成器的策略配置里有一个非常有用的开关:

.builder() .entityBuilder() .enableFileOverride() // 这是新版本才有的方法

默认情况下,生成器检测到同名文件已存在时不会覆盖,直接跳过。这是个非常安全的默认行为。如果你改了表结构,想重新生成某个实体类,就需要主动开启覆盖策略。但这里要特别小心:如果生成的Service里你自己添加了业务方法,覆盖之后这些方法全都没了。我遇到过同事在生成器里加了enableFileOverride(),然后重跑了一遍,结果把一个写了不少业务逻辑的Service实现类直接清空了,差点重构一周的量。

所以我的建议是:只有万不得已才开启全量覆盖。日常使用中,我把生成Service的策略改成不覆盖,把Entity和Mapper的策略改成覆盖,因为它们通常不包含人工编写的业务方法。这种"部分覆盖"的策略组合是最实用的。

6.5 数据库表缺失字段时的生成策略

很多场景下数据库表结构是不断演进的,比如新加了一个字段,希望同步到实体类。如果直接重跑覆盖,如上所述有风险。这里有另一个方案:只在对应的实体类里手动加上字段和@TableField注解,其他代码不动。MyBatis-Plus的BaseMapper不需要你为每个字段写SQL,新增字段后查询、插入、更新都会自动带上它,前提是实体类里字段写对了。

虽然这个方案不如"重跑生成"看起来自动化,但风险更低,也更符合实际开发节奏。

7. 生成完代码之后,照着这份清单检查一遍再提交

跑通生成流程只是开始,生成的代码能不能用、规范不规范,还需要花几分钟检查。我把项目里踩过的坑整理成一个检查清单,每次生成完代码之后对照着过一遍,能避免很多线上问题。

  • 检查实体类是否继承了Model。如果你的项目使用了MyBatis-Plus的Model体系(实体类继承Model<User>),生成策略的entityBuilder里没有默认开启superClass,生成的类是纯POJO。如果要嵌Model体系,需要自行配置。
  • 检查逻辑删除字段是否有@TableLogic注解。如果表里字段是deleted但实体类没有这个注解,说明策略配置里的logicDeleteColumnName没匹配上。
  • 检查乐观锁字段。如果表里有version字段,生成器不会自动加@Version注解,需要手动补充。MyBatis-Plus的乐观锁插件依赖这个注解才生效。
  • 检查主键策略。MyBatis-Plus默认主键策略是ASSIGN_ID(雪花算法),但如果表的主键是自增的,需要给@TableId加上type = IdType.AUTO。生成策略里可以通过entityBuilder().idType(IdType.AUTO)全局设置。
  • 检查Controller方法上的注解。默认生成的@GetMapping、@PostMapping路径是否符合团队的RESTful规范。
  • 检查Swagger注解。如果项目集成了Swagger,实体类的@ApiModelProperty一般会从数据库注释自动带过来,但Controller方法上的@ApiOperation需要确认是否生成,或者模板里定制。
  • 检查Mapper XML的namespace。生成的XML文件namespace应该和Mapper接口全限定名一致,如果不一致,启动或运行时必然报错。

这个清单不是让你逐条背诵,而是在项目提交之前花三五分钟扫一遍,特别是逻辑删除、乐观锁、主键策略这三个地方,因为它们不报错,但行为可能不符合预期,定位成本比报错类问题高得多。

8. 常见报错与解决思路:把我在实践中遇到的情况一次性说清楚

无论版本怎么选,跑生成器总会有翻车的时候。下面几个错误是我在实践和带团队过程中遇到的最高频问题,整理出来供参考。

8.1 表不存在或参数@TableId无效

这个报错通常长这样:

Error: Table 'sys_user' doesn't exist

或者你在生成的实体类上加了@TableId注解但运行时报@TableId无效。最常见的两个原因:第一,数据库连接字符串里database名称写错,连到了别的库,自然找不到表;第二,你使用了addInclude但表名写错了,比如实际表名带前缀,你写漏了。

还有一种情况是MySQL大小写敏感问题。Linux环境下MySQL的表名默认区分大小写,Windows默认不区分。如果你的表叫SYS_USER,但在addInclude里写sys_user,Windows上可能没感觉,Linux上就查不到。解决方法是严格保持表名和实际一致,或者在设计阶段就统一规范(通常全小写下划线分隔)。

8.2 生成文件为空或只有目录没有内容

出现这种问题,大概率是模板引擎配置出问题。比如你用Freemarker或Beetl引擎,但没有引入对应的依赖。而Velocity引擎是内置的,默认不需要额外引入。如果要用其他引擎,pom里要加依赖,同时在templateEngine里指定对应的FreemarkerTemplateEngine或BeetlTemplateEngine。

如果依赖都齐全但生成的文件还是空壳,检查一下策略配置里有没有把对应的生成项关闭。比如你配置了entityBuilder().disable(),那实体类就不会生成。

8.3 生成的XML拷贝到resources找不到

正如前面提到,Invalid bound statement (not found)这个错误100%和Mapper XML的加载路径有关系。检查顺序是:

  1. application.yml里的mybatis-plus.mapper-locations是否包含classpath*:mapper/**/*.xml
  2. XML文件是否确实在src/main/resources/mapper目录下
  3. XML文件里的namespace是否与Mapper接口的全限定名一致
  4. @MapperScan的包路径是否覆盖到了Mapper接口

我遇到过一种情况是@MapperScan写成了com.example.demo导致扫描到太多包,启动慢不说,还会出现Bean冲突。精确到mapper包是最稳妥的。

8.4 数据库时间字段生成了LocalDateTime但前端传参报错

这是我们项目线上真实遇到的一个问题。生成器用TIME_PACK策略生成LocalDateTime后,Controller接收前端传的时间字符串时,默认情况下Jackson不认识像"2024-01-01 12:00:00"这种格式,会报Cannot deserialize value of type java.time.LocalDateTime。

解决办法是在项目里统一配置Jackson的序列化和反序列化格式:

@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder -> { builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; } }

或者直接在SpringBoot的application.yml里配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

但date-format这个配置对LocalDateTime无效,它只对java.util.Date生效。所以如果实体类用LocalDateTime,必须走上面那种定制化的方案。这是个很隐蔽的坑,网上教程很少提到,我在集成阶段被它折腾过不少时间。

8.5 没有XML文件但控制台报错找不到XML

如果你用MyBatis-Plus生成Mapper接口,但表上只有简单的CRUD需求,其实可以不用XML文件。MyBatis-Plus的BaseMapper已经内置了所有单表CRUD方法。但如果出现了Invalid bound statement,说明某个方法确实需要XML里的SQL映射,而文件没加载。这时候要么补XML映射,要么检查是否在Mapper接口里手工定义了一个方法名和XML里的id不匹配。

顺带一提,mapper-locations这个配置不是必须的——如果你完全不使用XML映射,不配置也行。但一旦用了XML,必须配。我建议新项目统一保留这个配置项,因为后面总会有复杂SQL需求。

9. 实测下来的完整执行流程和最终目录结构

在这里我把自己跑通整个流程的完整命令顺序和目录结构贴出来,方便你照葫芦画瓢。

从零到一的关键步骤是:

  1. 在pom.xml添加mybatis-plus-boot-starter和mybatis-plus-generator依赖
  2. 创建生成器类CodeGenerator,配置数据源、包路径、策略
  3. 运行生成器main方法
  4. 确认生成的Java文件和XML文件落位正确
  5. 在启动类添加@MapperScan注解
  6. 在application.yml配置mapper-locations和数据源
  7. 启动项目,调用一个生成好的接口验证

最终项目的目录结构大概长这样:

src/main/java/com/example/demo ├── DemoApplication.java ├── common/ // 通用类,如统一返回结果、异常处理 └── system/ ├── controller/SysUserController.java ├── service/ISysUserService.java ├── service/impl/SysUserServiceImpl.java ├── mapper/SysUserMapper.java └── entity/SysUser.java src/main/resources ├── application.yml └── mapper/SysUserMapper.xml

注意生成的Controller类名可能是SysUserController。因为表名sys_user在去掉了sys_前缀后,实体名是User,但Controller的命名规则取决于你有没有额外配置。

如果你不想去掉表前缀,那生成结果就是:

entity/SysUser.java mapper/SysUserMapper.java service/ISysUserService.java service/impl/SysUserServiceImpl.java controller/SysUserController.java

哪种更好完全取决于你的项目命名习惯。团队里用的什么风格,就保持什么风格。代码生成器的价值在于减少重复劳动,它不应该替你做架构决策。

10. 最后聊几点我个人的使用体会

用这个生成器已经有几年了,从最早的AutoGenerator到现在的FastAutoGenerator,中间代码替我省下的时间是不可估量的。但我不主张把它当作"银弹"。它本质上是一个辅助工具,解决的是80%的机械性编码工作,剩下20%的业务逻辑、权限控制、异常处理、事务边界这些真正体现代码质量的部分,还是需要人来完成。

有几个经验分享给刚上手的读者:

第一,生成器适合在项目初期或者新模块设计完成之后立刻使用,不要在模块开发到一半才去生成,那时候代码已经写了不少,重跑生成器会有覆盖风险。

第二,建议每个团队维护一份统一的CodeGenerator配置类,放进项目代码库里,而不是每个人各写一份。这样大家生成出来的代码风格完全一致,不会出现包路径五花八门、命名规则各不相同的情况。

第三,如果你的表结构比较复杂,比如有大量联合唯一索引、复合主键或者自定制的字段类型,生成器产出的代码可能不完全满足需求。这种情况下,生成作为"初稿"然后用人工修改,效率依然远高于从零手写。

最后再说一个很多人不知道的小技巧:你完全可以在同一个项目里配置多套生成器,用不同的parent包名,指向不同的数据库。比如一套生成master库的代码,一套生成biz库的代码,只要在packageConfig里区分开包名就行,生成结果互不干扰。这个技巧在对接三方系统时非常实用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询