尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

新版MyBatis-Plus代码生成器FastAutoGenerator实战指南 写Java后端的人大概都有被CRUD支配过的经历。实体类加一个字段Controller、Service、Mapper、XML全要跟着动新表建好光把那套标准文件补齐就要小半天。我第一次用MyBatis-Plus代码生成器时只当它是个快速生成类的小工具后来在项目里用熟了才发现它的真正价值不在于省几行代码而是把团队几十个模块的CRUD风格拉齐让每个人生成的类长得一模一样。最近MyBatis-Plus把代码生成器重写成了FastAutoGenerator老教程里的AutoGenerator写法已经过时网上很多资料还停在旧版。这篇文章就结合我在新项目里的实操把新版代码生成器的配置、批量生成过程和落地时的几个关键经验一次讲完。1. 新版代码生成器为什么值得升级1.1 从AutoGenerator到FastAutoGeneratorAPI变在哪旧版代码生成器的痛点用过的人应该都有体会。整个生成流程要分别创建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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue几个关键参数要说一下。characterEncodingutf8是为了避免生成注释时出现中文乱码serverTimezone必须指定否则高版本MySQL驱动会报时区错误useSSLfalse和allowPublicKeyRetrievaltrue是MySQL 8.x的常见配置不加allowPublicKeyRetrieval容易遇到“Public Key Retrieval is not allowed”的报错。注意生成器只读取表结构不会修改数据库数据所以不用担心误操作。直接用项目所用的账号即可但建议有SELECT权限的账号就够用最小化权限是实操中的好习惯。驱动依赖别漏。MySQL 8及以上版本要用com.mysql.cj.jdbc.Driver项目的JDBC连接下不需要显式设置driverClassNameFastAutoGenerator能根据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.entityService实现路径是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/setterenableTableFieldAnnotation让实体字段标注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 groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator/artifactId version3.5.7/version /dependency dependency groupIdorg.freemarker/groupId artifactIdfreemarker/artifactId version2.3.32/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue; 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的IServiceServiceImpl继承了ServiceImpl这意味着基础CRUD方法全部内置不需要自己实现。Controller默认生成了一组标准接口包含分页列表、详情、保存、更新、删除。分页列表方法的写法类似GetMapping(/page) public IPageTUser page(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size) { PageTUser 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 CommonCrudServiceT { Autowired private BaseMapperT baseMapper; public T selectById(Serializable id) { return baseMapper.selectById(id); } public ListT selectBatchIds(Collection? extends Serializable idList) { return baseMapper.selectBatchIds(idList); } public boolean insert(T entity) { return baseMapper.insert(entity) 0; } public boolean insertBatch(CollectionT 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 ListT list(WrapperT queryWrapper) { return baseMapper.selectList(queryWrapper); } }这个类的核心价值在于直接操作BaseMapper不依赖具体业务Service。方法全部是纯函数式的输入参数、输出结果、内部无状态。批量插入的时候要注意如果list非常大一次性遍历插入数据库连接压力大建议按每批200到500条切割循环处理public void insertBatchLarge(CollectionT entityList) { ListT list new ArrayList(entityList); int batchSize 500; for (int i 0; i list.size(); i batchSize) { ListT 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的常规问题连接串加上allowPublicKeyRetrievaltrueuseSSLfalse即可。遇到“Could not create connection to database server”优先检查时区参数serverTimezone是否配置。遇到“Table not found”之类的错误检查表名是否写错注意大小写敏感问题。我整理了一份问题速查表基本覆盖了日常使用中的高频故障。症状常见原因处理办法生成后找不到文件outputDir未设置或用了相对路径显式配置绝对路径到src/main/java重复生成不更新默认跳过已存在文件globalConfig开启enableFileOverridePublic Key Retrieval is not allowedMySQL 8公钥获取限制URL加allowPublicKeyRetrievaltrueuseSSLfalse实体类没有Lombok注解策略中未开启LombokentityBuilder().enableLombok()Controller是Controller不是RestController未开启REST风格controllerBuilder().enableRestStyle()Mapper XML扫描不到XML路径与配置不匹配项目配置mybatis-plus.mapper-locationsclasspath*:mapper/**/*.xml中文字段注释乱码连接串没有指定编码URL加characterEncodingutf85.4 生产级项目的小建议版本升级到3.5.x之后生成器配置和运行基本稳定但有几个小习惯建议养成分。生成器主类放独立目录不要放业务包最好标记为工具类因为在生产环境打包时不需要跑它。生成完的代码先编译一次再提交避免把错误文件推进仓库。生成器不需要每次启动项目都跑它只是开发阶段的一次性工具建好表后跑一次就够了以后表结构变更再重新执行。最后再分享一个我实际项目里的经验。如果团队有多个人都需要用到代码生成器不要让每个人都配一套生成规则而是由一个人把生成器主类配置好commit到仓库里其他人拉代码后只改数据库密码就能直接用。这样所有人生成出来的代码风格完全一致code review时也不用再因为个人习惯不同而争论格式问题了。我个人最开始接触代码生成器时也觉得可有可无但用习惯了之后它的收益体现在长期维护上。项目里表越多、模块越碎片化生成器带来的节省越明显。新表进来改一下addInclude的表名列表跑一下main方法一套标准代码直接落盘剩下的时间全部留给业务逻辑。这种投入产出比值得每个用MyBatis-Plus的Java后端同学花上一下午把这套流程搭起来。
返回列表