ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MyBatis-Plus代码生成器实战:SpringBoot集成FastAutoGenerator详解

MyBatis-Plus代码生成器实战:SpringBoot集成FastAutoGenerator详解 1. 为什么团队里总有人还在手写Mapper先聊聊代码生成这件事先说个我观察了很久的现象大多数Java后端项目里真正拖慢开发进度的往往不是业务逻辑本身而是那些结构高度雷同的基础代码。一个标准的数据访问层无非就是实体类、Mapper接口、XML文件或者Service层的CRUD模板这些代码几乎没有任何智力含量但每个新模块都要重写一遍。我见过不少项目组团队成员每天花两三个小时在复制粘贴上再把包名、类名、字段类型逐个改掉效率低不说还特别容易漏改。MyBatis-Plus的自动代码生成器解决的就是这个问题。它可以根据数据库表结构一键生成Entity、Mapper、Service、Controller这一整套基础代码而且生成的代码直接贴合MyBatis-Plus的API风格不用自己再去手写那些BaseMapper的实现。说白了它就是把你从机械劳动里解放出来把时间留给真正需要动脑的业务设计。这次我打算用一套完整的实操流程来演示怎么在SpringBoot项目里把它跑起来。适合谁看呢如果你刚接触MyBatis-Plus想在项目里引入代码生成但不知道从哪下手或者你已经用了MyBatis-Plus但一直是手写基础代码想提升效率再或者你带团队想统一团队的数据访问层代码规范——这篇文章应该都能给你一些参考。需要注意的是网上关于这个生成器的教程版本差异非常大老版本用AutoGenerator新版本迁到了FastAutoGeneratorAPI变化不小。我这次直接基于目前主流的新版本写法把关键配置一点一点拆开讲清楚。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改jakarta3.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 groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator/artifactId version3.5.3.1/version /dependencySpringBoot 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) ) ENGINEInnoDB AUTO_INCREMENT1 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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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接口默认继承IServiceUser实现类默认继承ServiceImplUserMapper, 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生成了Integerdecimal生成了BigDecimal。其实这些映射关系MyBatis-Plus有固定的规则一般来说不需要手动干预但了解规则有助于排查问题。数据库字段类型生成的Java类型varchar / charStringint / integerIntegerbigintLongtinyintIntegersmallintShortdecimal / numericBigDecimalfloatFloatdoubleDoubleboolean / bitBooleandateLocalDateTIME_PACK策略下timeLocalTimeTIME_PACK策略下datetime / timestampLocalDateTimeTIME_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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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: 0mapper-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 deleted0这条逻辑删除感知的查询。我第一次跑通这个流程时遇到的问题是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让所有接口直接返回ResultT这种统一格式。在策略配置里可以这样改.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体系实体类继承ModelUser生成策略的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 doesnt exist或者你在生成的实体类上加了TableId注解但运行时报TableId无效。最常见的两个原因第一数据库连接字符串里database名称写错连到了别的库自然找不到表第二你使用了addInclude但表名写错了比如实际表名带前缀你写漏了。还有一种情况是MySQL大小写敏感问题。Linux环境下MySQL的表名默认区分大小写Windows默认不区分。如果你的表叫SYS_USER但在addInclude里写sys_userWindows上可能没感觉Linux上就查不到。解决方法是严格保持表名和实际一致或者在设计阶段就统一规范通常全小写下划线分隔。8.2 生成文件为空或只有目录没有内容出现这种问题大概率是模板引擎配置出问题。比如你用Freemarker或Beetl引擎但没有引入对应的依赖。而Velocity引擎是内置的默认不需要额外引入。如果要用其他引擎pom里要加依赖同时在templateEngine里指定对应的FreemarkerTemplateEngine或BeetlTemplateEngine。如果依赖都齐全但生成的文件还是空壳检查一下策略配置里有没有把对应的生成项关闭。比如你配置了entityBuilder().disable()那实体类就不会生成。8.3 生成的XML拷贝到resources找不到正如前面提到Invalid bound statement (not found)这个错误100%和Mapper XML的加载路径有关系。检查顺序是application.yml里的mybatis-plus.mapper-locations是否包含classpath*:mapper/**/*.xmlXML文件是否确实在src/main/resources/mapper目录下XML文件里的namespace是否与Mapper接口的全限定名一致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: GMT8但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. 实测下来的完整执行流程和最终目录结构在这里我把自己跑通整个流程的完整命令顺序和目录结构贴出来方便你照葫芦画瓢。从零到一的关键步骤是在pom.xml添加mybatis-plus-boot-starter和mybatis-plus-generator依赖创建生成器类CodeGenerator配置数据源、包路径、策略运行生成器main方法确认生成的Java文件和XML文件落位正确在启动类添加MapperScan注解在application.yml配置mapper-locations和数据源启动项目调用一个生成好的接口验证最终项目的目录结构大概长这样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里区分开包名就行生成结果互不干扰。这个技巧在对接三方系统时非常实用。
返回列表