若依集成Mybatis-Plus代码生成器:完整实践与避坑指南

若依集成Mybatis-Plus代码生成器:完整实践与避坑指南 简介若依前后端分离版集成Mybatis-Plus代码生成器资料包面向需要在若依框架中引入Mybatis-Plus的Java后端开发者尤其适合已掌握Spring Boot基础、希望提升企业级项目开发效率的中级工程师。资源覆盖代码生成器配置、基础代码自动生成、自定义代码冲突处理等实操难题从环境搭建到持续迭代均给出可循路径。压缩包共含2010个文件其中1605个Markdown文档为学习笔记与排错记录251个Java文件为实体类、Mapper、Service、Controller等分层代码示例另有JS、XML、SQL、Properties等配置脚本整体71.55MB。已有536人学习下载。资料专门收录了若依环境使用手册并结合大量Markdown笔记与Java源码梳理了生成器运行相关的目录结构、配置参数与常见排错思路读者可据此快速搭建可运行示例工程理解自动生成代码与自定义代码的边界。无论是初次集成还是后期因数据库表结构变更而重新生成代码这份资料都能提供清晰的参照路径帮助团队统一规范、加速交付。 做后端开发这几年我接触最多的脚手架之一就是若依尤其是前后端分离版。它在单项目实战、学习、甚至交付项目时都特别能打内置了用户、角色、菜单、字典、操作日志这些基础功能配合Vue前端十分钟就能把一套后台管理系统跑起来。但有一个环节让我一直不太满意自带的那套代码生成器虽然好用可生成的是基于MyBatis XML手写SQL风格的代码遇到逻辑删除、公共字段自动填充、复杂查询条件写起来还是绕。最近我花了两天时间把Mybatis-Plus代码生成器集成到了若依前后端分离版里把生成逻辑从MyBatis风格切换成MyBatis-Plus风格整个过程踩了不少坑也整理出一套可以直接复用的方案。这篇文章就把这次集成的完整过程、核心配置、模板思路以及常见问题都记录下来适合正在用若依做二次开发、或者想给团队统一技术栈的同学参考。1. 为什么要替换若依自带的代码生成器1.1 若依自带生成器的现状与痛点若依前后端分离版内置的代码生成器在单表CRUD场景下效率很高只要在表结构上配置好字段类型、表单类型、查询方式就能生成前端Vue列表、后端Controller、Service、Mapper以及对应的XML文件。对常规业务来说这套生成器完全能用真正让我想换掉它的原因是生成代码的风格和后续维护成本。若依默认生成的是XML写SQL的风格Mapper里没有现成的通用方法很多简单查询也要自己在XML里写where条件。一旦表字段变动要么重新生成覆盖要么在XML里手动改改完还得担心之前的自定义SQL被冲掉。多对多、主子表这类稍微复杂一点的业务自带的生成器几乎帮不上忙。另外一点是团队技术栈的统一问题。现在很多项目组已经全面切到Mybatis-Plus用LambdaQueryWrapper、分页插件、逻辑删除这些能力来提效。如果脚手架还是MyBatis原生风格新人来了还要学两套写法代码风格也不一致。与其在若依原生成器上二次魔改不如直接把Mybatis-Plus代码生成器接进来一次投入后续所有新模块都按这个模式走。1.2 Mybatis-Plus代码生成器能做什么Mybatis-Plus的定位是“只为简化开发而生”它没有替代MyBatis而是在MyBatis的基础上做了大量增强。最大的价值是BaseMapper和IService继承之后你不需要写任何SQL就能完成单表的增删改查、批量操作、分页查询。配上LambdaQueryWrapper查询条件用类型安全的方式写字段名写错会在编译期就暴露而不是等到运行时才发现SQL拼错了。分页插件也非常省心不用手动拼limit调用一条Page查询就能返回记录列表和总数。代码生成器则是把这些能力固化下来。填好数据源、包名、表名它会生成实体类、Mapper接口、Service接口、ServiceImpl实现类、Controller以及对应的Mapper XML如果用Mybatis-Plus风格XML基本可以不生成。和若依的生成器不同Mybatis-Plus生成器对模板完全开放默认用FreeMarker或Velocity模板你可以照着若依的Controller格式改模板生成出来的代码直接带PreAuthorize菜单权限注解、返回若依的AjaxResult或TableDataInfo零手工调整直接用。1.3 集成前的边界确认这里要先说清楚一个思路我并不是把若依原生的MyBatis整个换掉而是让Mybatis-Plus基于同一个SqlSessionFactory去工作。若依的系统管理模块也就是用户、角色、菜单、字典这些还在用原生Mapper和XML代码稳定且经过了大量验证没必要动。新业务模块使用Mybatis-Plus生成器也只针对新业务表。这样一个项目里两套写法共存互不干扰迁移成本最低。如果连系统模块都想切到Mybatis-Plus那就要另外处理Mapper兼容和XML冲突问题工作量会大很多。我个人建议先按共存方案做跑通了再逐步替换风险可控。2. 环境准备与依赖注入2.1 梳理若依版本和模块结构我用的版本是RuoYi-Vue 3.8.xSpring Boot版本2.5.15JDK 1.8。若依前后端分离版的Maven结构大概这样ruoyi-admin是启动模块ruoyi-framework里放框架层配置ruoyi-system是系统管理模块ruoyi-common是公共包ruoyi-quartz是定时任务模块ruoyi-generator是若依自带的代码生成器模块。如果你不用若依自带的生成器生成器模块可以直接保留不影响主流程。需要特别注意的是Mybatis-Plus版本必须和Spring Boot 2.5.x匹配。我实测用的是mybatis-plus-boot-starter 3.5.3.1配合mybatis-plus-generator 3.5.3.1和freemarker模板引擎整条链路没有遇到版本冲突。如果你的项目是Spring Boot 3.x那要换到Mybatis-Plus的spring-boot3专用依赖基础配置逻辑不变但细节会有差异。2.2 Mybatis-Plus核心依赖和版本选择在若依的ruoyi-common模块或者你想放公共配置的模块里引入依赖。这里有一个细节如果之前引入了mybatis-spring-boot-starter不要手动excludeMybatis-Plus官方依赖会接管MyBatis的初始化。下面是我用的Maven依赖。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency代码生成器依赖这个依赖一般只在本地跑生成时用所以没必要打进最终jar放到单独的tools模块或者用test作用域隔离。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator/artifactId version3.5.3.1/version /dependency dependency groupIdorg.freemarker/groupId artifactIdfreemarker/artifactId version2.3.32/version /dependency这里要强调一点代码生成器依赖不要放在ruoyi-admin这种运行时模块里否则生成的jar包里会多出一堆不该有的东西而且启动时还可能触发一些奇怪的类加载问题。我习惯单独建一个ruoyi-generator-plus的Maven模块只负责跑生成器生成的代码直接按包路径写入到各个业务模块。2.3 分页插件与自动填充配置Mybatis-Plus接入Spring Boot后如果你不显式注入分页插件分页查询是会出问题的常见表现是查出来的total是0或者只查出一条数据原因就是分页拦截器没有生效。在ruoyi-framework里找一个已有的Config类注册MybatisPlusInterceptor即可。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setOverflow(false); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }另外如果想让createTime、updateTime、createBy、updateBy这些公共字段自动填充还需要定义一个MetaObjectHandler。特别是当生成的实体类继承了若依的BaseEntity字段在父类里MetaObjectHandler照样能处理因为Mybatis-Plus在解析表字段时会包含继承链中的字段。下面是自动填充的配置。Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, Date.class, new Date()); this.strictInsertFill(metaObject, updateTime, Date.class, new Date()); this.strictInsertFill(metaObject, createBy, String.class, SecurityUtils.getUsername()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, Date.class, new Date()); this.strictUpdateFill(metaObject, updateBy, String.class, SecurityUtils.getUsername()); } }注意这里引用了若依的SecurityUtils工具类来拿当前登录人如果你的服务里没有这个工具类需要自己从上下文里取或者暂时不填createBy。整体方案以跑通为主不用追求所有字段都自动填充。3. 代码生成器的核心配置与模板编写3.1 生成器策略表与包路径的配置FastAutoGenerator是Mybatis-Plus生成器的推荐入口。配置项看起来很多其实核心就四块数据源、全局配置、包配置、策略配置。我给的这套配置是直接在独立模块里跑main方法生成后自动把类写到对应的业务模块源码目录下。设计的关键全局配置里的outputDir指向若依业务模块的java目录下比如 ruoyi-system/src/main/java并不是指向自己模块。包配置里的parent填业务模块的根包名比如 com.ruoyi.business。策略配置里tablePrefix填写表前缀比如 bus_生成的实体类会自动去掉前缀。需要开启lombok因为若依项目默认引入了lombok生成代码可以少写一堆getter和setter。开启swagger注解配置因为若依自带knife4j或springfox生成的实体字段上会带ApiModelProperty注解。FastAutoGenerator.create(jdbc:mysql://localhost:3306/ruoyi?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai, root, password) .globalConfig(builder - { builder.author(yourName) .outputDir(D:/workspace/ruoyi-vue/ruoyi-system/src/main/java) .disableOpenDir(); }) .packageConfig(builder - { builder.parent(com.ruoyi.business) .entity(domain) .service(service) .serviceImpl(service.impl) .mapper(mapper) .controller(controller); }) .strategyConfig(builder - { builder.addInclude(bus_order, bus_order_detail) .addTablePrefix(bus_); }) .templateEngine(new FreemarkerTemplateEngine()) .execute();3.2 自定义模板如何生成符合若依风格的代码默认的FreeMarker模板生成出来的Controller不带若依的PreAuthorize权限注解返回结构也不是若依的AjaxResult和TableDataInfo直接在若依里跑起来会怪怪的。所以模板这步非常重要相当于把Mybatis-Plus的生成器改造成若依形状。我的做法是从mybatis-plus-generator的jar包里把默认模板解压出来然后在resource/templates目录下自建一套。重点修改三个模板controller.java、serviceImpl.java、entity.java。自定义模板的目录结构src/main/resources/templates/ ├── entity.java.ftl ├── mapper.java.ftl ├── service.java.ftl ├── serviceImpl.java.ftl └── controller.java.ftl模板引擎会自动去resource/templates下找同名模板没有找到才用默认模板所以你可以只放要改的那几个文件。3.3 核心模板改造思路entity模板建议让实体类继承若依的BaseEntity这样自动带上搜索参数、createTime、remark等基础字段不用自己再定义。同时生成Mybatis-Plus注解TableName、TableId(type IdType.ASSIGN_ID)并给表字段加上Excel注解方便若依导出功能直接读取。生成的字段名要注意下划线转驼峰Mybatis-Plus默认会做这个映射所以实体类字段名用驼峰即可。Controller模板是最重要的改造点。若依的Controller统一风格是list方法接收PageDomain参数调用startPage()之后查List最后返回getDataTable(list)新增、修改用RequestBody接收实体返回AjaxResult.success()接口上标注PreAuthorize(ss.hasPermi(business:order:list))。这里我没有沿用startPage()因为startPage()和Mybatis-Plus分页插件会冲突我会在下一节专门讲分页对接。模板里list方法直接接收Mybatis-Plus的Page查询返回结构转成若依的TableDataInfo然后再返回。ServiceImpl模板改造主要是把默认继承的ServiceImplInteger, T改成对应的实体和Mapper泛型并在方法里用LambdaQueryWrapper去写查询条件避免再生成XML文件。如果你不想写XML生成器里可以把Mapper XML生成关掉这样项目目录里不会出现无用的XML文件。4. 实操生成与后端集成4.1 执行生成与模块落位整个生成过程其实就是一个本地main方法。配置好数据源和输出目录后直接跑一次生成的多个类会自动分到ruoyi-system里对应的domain、mapper、service、controller包下。第一阶段需要注意如果表名带前缀生成的包名和类名默认不会带前缀这点一定要在配置里确认好否则生成的Mapper接口跟XML路径会对不上。确认无误后把生成的代码同步到开发分支启动项目验证基础CRUD。生成完以后在业务Controller上先别急着启动检查一下PreAuthorize里的权限字符串是否跟菜单管理里配的一模一样。比如你在菜单管理里添加的是business:order:list模板里也要输出business:order:list。一旦不一致接口会被安全框架拦截表现成前端一直401或403排查起来很费时间。4.2 分页接口对接若依的TableDataInfo这是本次集成中最重要的适配点。若依前端分页组件期望的返回格式是{code:200, rows:[], total:0}对应后端TableDataInfo对象。而Mybatis-Plus的Page查询返回的是com.baomidou.mybatisplus.extension.plugins.pagination.Page结构不一样。所以要把Page转成TableDataInfo可以放在Controller里写一个工具方法或者直接在继承的BaseController里加一个convertPage方法。protected TableDataInfo getDataTable(IPage? page) { TableDataInfo rspData new TableDataInfo(); rspData.setCode(HttpStatus.SUCCESS); rspData.setMsg(查询成功); rspData.setRows(page.getRecords()); rspData.setTotal(page.getTotal()); return rspData; }然后Controller里这样写PreAuthorize(ss.hasPermi(business:order:list)) GetMapping(/list) public TableDataInfo list(BusOrder order, PageDomain pageDomain) { PageBusOrder page new Page(pageDomain.getPageNum(), pageDomain.getPageSize()); LambdaQueryWrapperBusOrder wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(order.getOrderNo()), BusOrder::getOrderNo, order.getOrderNo()); IPageBusOrder result busOrderService.page(page, wrapper); return getDataTable(result); }注意这里没有用若依的startPage()方式因为startPage()依赖的是原生MyBatis分页插件跟Mybatis-Plus分页插件会冲突。两种分页机制不能混用在一个请求链路里用了startPage之后再用Mybatis-Plus的Page结果会非常诡异总数错、数据错、偶发还报内存溢出。这条一定记住。4.3 权限与登录信息兼容若依的安全体系是基于Spring Security加JWT的登录用户信息放在SecurityContext中业务代码里用SecurityUtils.getLoginUser()就能拿到当前登录对象。Mybatis-Plus自动填充里如果要用createBy直接引SecurityUtils.getUsername()即可不用额外适配。前提是接口要经过若依的登录过滤器也就是客户端请求时要带Authorization请求头。生成的Controller如果带了PreAuthorize注解接口在未登录状态下会先被Spring Security拦下来返回401。这其实是若依的正常行为不是集成问题。如果你在调试Knife4j接口文档时发现所有新生成的接口都要先登录可以在配置里放开测试路径或者用登录后的token去调这是最稳妥的方式。5. 常见问题与避坑记录5.1 Mapper XML路径不对导致启动报错把Mybatis-Plus接进来后最容易遇到的就是“Invalid bound statement (not found)”。这种情况九成是mapper-locations路径配置不对。若依原来的配置一般在application.yml里是classpath*:mapper/**/*Mapper.xml。Mybatis-Plus默认找classpath*:/mapper/**/*.xml路径不一致就会出现绑定失败。处理方式很简单在你的配置里把mapper-locations显式指定。mybatis-plus: mapper-locations: classpath*:mapper/**/*Mapper.xml configuration: map-underscore-to-camel-case: true还有一类坑是Mapper接口所在包没有被扫描到。若依启动类上已经有MapperScan(com.ruoyi.**.mapper)这个写法能扫到所有子包所以新业务模块的Mapper包只要放在com.ruoyi下面就能扫到。如果放到了别的根包记得在启动类上补充扫描路径。5.2 分页不生效或total为0如果你配置了MybatisPlusInterceptor但分页还是不对优先检查三个地方第一拦截器里设置的数据库类型是否和实际数据库一致MySQL用DbType.MYSQLPostgreSQL要改成PG第二是否不小心注册了多个MybatisPlusInterceptor重复注册会导致拦截器执行顺序异常第三查询时传入的Page对象是否new了但没传给Mapper或Service方法很多时候是忘了把page作为第一个参数传进去。total为0还有一个隐蔽原因表里没有主键或者主键没有正确映射到实体类字段。Mybatis-Plus分页插件需要识别主键来做count识别不了就会返回0。给表加上主键并在实体类里标注TableId基本都能解决。5.3 代码生成后被覆盖或重复生成代码生成器跑第二次的时候默认不会自动覆盖同名类但也存在一定的不一致性。我的习惯是让生成器输出到一个临时目录比如target/generator-output对比后手动拷贝到业务模块。这样风险最小不容易把已经改过的代码冲掉。要是确实希望直接覆盖可以在全局配置里打开fileOverride开关但我强烈建议不要对正在开发的模块直接覆盖。另外生成的Service接口和ServiceImpl类里如果有自己追加的业务方法下次重新生成时会被覆盖。所以再次生成之前先看一下git diff把手工加的代码备份或迁走生成完再合并回来。这个教训我是真金白银买来的生成器一跑几小时的手写代码说没就没。5.4 与若依自带工具类的冲突处理若依自带了一些分页配套工具类比如TableSupport、PageUtils这些工具都是基于原生MyBatis的PageHelper实现。切到Mybatis-Plus后如果业务代码还在用PageUtils会在返回结构上打架。新业务模块统一不要用若依自带的PageUtils分页全部走Mybatis-Plus的Page加自定义getDataTable转换。还有Mybatis-Plus的逻辑删除。若依的表结构里很多都有del_flag字段可以用TableLogic注解来实现逻辑删除。这样deleteById方法会自动变成update语句而不是物理delete对数据安全很有利。但要注意若依系统表里del_flag的取值规则是0存在、1删除配置里要写清楚逻辑删除的值别用默认的1和0反了删了之后查不到数据才发现就晚了。这次集成给我最大的感受是若依这个脚手架最大的价值不在于它自带的代码生成器有多强而在于它把权限、多租户、定时任务、日志这些通用能力都打通了。在这个基础上接Mybatis-Plus能让新业务的开发速度再上一个台阶。模板这块建议团队里专门抽一个人维护以后有新的公共字段、返回格式调整只需要改模板重新生成一遍就能全项目同步。最后再分享一个小技巧生成后先不要急着做复杂查询先把单表CRUD跑通确认分页、权限、自动填充都正常再往模板里加主子表、关联查询这类复杂功能这样排查问题会轻松很多。希望这次的配置和避坑记录能帮到正在折腾这件事的同行少走一点我走过的弯路。本文还有配套的精品资源点击获取