资讯动态

MybatisPlus代码生成器实战:分页失效与Irepository配置避坑

发布时间:2026/9/19 3:48:32 来源:尧图企业网站定制
用 MybatisPlus 代码生成器这半年我在三个项目里从零到一把它跑通中间踩了不少坑。最近逛社区又看到有人在问“分页失效”“Irepository 到底怎么用”“单页 500 条限制怎么接触”这些问题其实都和代码生成器的配置习惯有直接关系。这篇文章我就把整套玩法拆开讲清楚从生成器怎么配、生成完的代码怎么改到几个高频坑点的排查思路一次说完。先说清楚这篇文章适合谁看项目里已经用了 MybatisPlus但还在手写 entity、mapper、service 的或者已经引了代码生成器但生成的代码不敢直接改、分页一查就出问题的。我会按实际工程中遇到的顺序来讲尽量少讲概念多给能直接抄走的配置和思路。1. 代码生成器解决的根本问题不是省那几行 CRUD1.1 生成器的定位基础代码的“标准件车间”很多人有一个误解觉得代码生成器就是帮你把增删改查的接口自动生成了省得手写。这个理解不算错但格局小了。它真正帮你解决的是“团队代码风格统一”的问题——每个实体类都按同一套规则生成字段注解怎么写、逻辑删除怎么标、乐观锁版本号叫什么名全部由模板说了算而不是靠每个开发自觉。我们团队之前在实体类命名上就出过乱子有人用createTime有人用created_at还有人干脆写拼音。自从统一用生成器之后模板里把驼峰转下划线的规则定死生成出来的TableField注解全部一致Review 代码时不会再因为这种鸡毛蒜皮浪费口水。1.2 适合引入生成器的项目类型不是所有项目都适合上一套代码生成器。我建议按下面这个标准来判断表结构相对稳定、字段数量多的业务系统尤其是后台管理类项目值得引。快速迭代的 ToC 前端接口服务需求一天三变表和字段经常调整生成器反而会成为负担——每次改表都要重跑一次不如直接改实体类来得快。新项目冷启动阶段实体、Mapper、Service 都还没定稿这时候用生成器跑一遍等于把基础骨架搭好后面改起来有底。老项目改造如果原表命名混乱建议先做表结构调整再上生成器否则生成出来的注解会非常难看改注解的工作量比手写还大。1.3 生成器覆盖的内容比你想象的多代码生成器不只是生成一个实体类。我用的版本3.5.x支持生成这些内容entity 实体类带 MybatisPlus 注解和 Swagger 注解。mapper 接口继承 BaseMapper。mapper XML 文件里面预置了结果映射和基础查询片段。service 接口和 serviceImpl 实现类可以直接继承 IService 和 ServiceImpl。controller 控制器内置分页查询、按 ID 查询、新增、修改、删除的标准接口。如果你还额外配置了 DTO/VO 模板也能生成对应的对象类。也就是说一个表从数据库到 Rest 接口中间这一层代码全部可以一键生成。你要做的只是在 controller 里加上业务校验逻辑在 service 里补充事务和缓存。2. 代码生成器的接入方式与参数解读2.1 方式一main 方法直跑学习成本最低最常见的接入方式是在工程里单独建一个test包或tools包放一个普通 main 方法里面调用AutoGenerator。这种方式的好处是直观、不需要额外装插件坏处是每次都要手动改url和表名适合个人开发或一次性生成。public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create(jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai, root, 123456) .globalConfig(builder - builder .author(你的名字) .outputDir(System.getProperty(user.dir) /src/main/java) .enableSwagger() ) .packageConfig(builder - builder .parent(com.example.demo) .entity(entity) .service(service) .serviceImpl(service.impl) .mapper(mapper) .controller(controller) ) .strategyConfig(builder - builder .addInclude(sys_user, sys_role) .addTablePrefix(sys_) .entityBuilder().enableLombok().enableTableFieldAnnotation() .controllerBuilder().enableRestStyle() ) .execute(); } }这套代码我实际用了挺长时间几个配置项要注意一下outputDir建议写成System.getProperty(user.dir) /src/main/java这样不管在哪台机器上跑都能定位到当前工程的源码目录。addTablePrefix(sys_)会把表名前缀去掉生成User而不是SysUser如果你的表统一带前缀这个配置能省不少改名的事。enableRestStyle()会把 controller 改成RestController风格避免每个类上还要手动补ResponseBody。2.2 方式二Maven 插件集成团队协作更规范如果你的团队已经用 Maven 管理依赖我更推荐把生成器配成 Maven 插件。这样每次生成用的都是同一份配置不会出现“张三本机跑出来的注解风格和李四不一样”的情况。plugin groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator-maven-plugin/artifactId version3.5.5/version configuration jdbcUrljdbc:mysql://localhost:3306/demo/jdbcUrl usernameroot/username password123456/password basePackagecom.example.demo/basePackage includesys_user,sys_role/include tablePrefixsys_/tablePrefix /configuration /plugin配好之后执行mvn mybatis-plus-generator:generate就能跑。这个方式的优点在于配置和代码分离项目换人或迁移环境时不用重新讲解缺点是不如 main 方法灵活如果你想临时生成一张小表还需要临时改配置。2.3 关键参数背后到底在控制什么生成器的参数看起来多核心其实就四块数据源、包路径、表策略、实体风格。我按实际踩坑的经验给你划一下重点。数据源配置里url后面的serverTimezone参数建议必须带上否则高版本 MySQL 驱动会报时区错误。useSSLfalse在本地开发环境建议加不然会有 SSL 握手警告虽然不影响运行但日志很难看。包路径配置决定代码落在哪个包下面。注意service和serviceImpl不能配成同一个包否则接口和实现类会挤在一起代码结构会很乱。表策略里addInclude是白名单只生成你指定的表addExclude是黑名单除了指定的表都生成。我个人的习惯是新表用addInclude只生成需要的避免把不需要的表对象也塞进工程里大范围重构时才用addExclude。实体风格相关配置我自己是加了enableLombok和enableTableFieldAnnotation。前者让实体类摆脱一堆getter/setter后者保证每个字段都有TableField注解这样如果以后字段名调整MybatisPlus 能精确映射不会出现“字段名对不上查出来全是 null”的怪问题。2.4 模板引擎怎么选Freemarker 还是 Velocity默认的模板引擎是 Velocity但我在实际项目中换成了 Freemarker。原因很简单Freemarker 的模板语法对复杂逻辑处理更顺手尤其是生成 XML 文件时要做循环、判断条件时直观很多而且 MybatisPlus 官方对 Freemarker 的适配也成熟不容易踩编码的坑。mybatis-plus: generator: template-engine: freemarker如果只是在生成的 entity 上加个 Swagger 注解默认模板就够了一旦你想自定义生成内容的风格比如给所有实体加一个公共父类或者把生成的 controller 改成你们团队的统一返回类型RT那就必须动模板。建议新手直接用 Freemarker网上参考模板也多遇到问题好搜。3. 生成完代码之后的工程结构与改造思路3.1 生成后的代码在哪看、怎么看生成器跑完会在你配置的outputDir下按包路径建目录。如果你用的parent是com.example.demo那么src/main/java/com/example/demo ├── controller │ └── SysUserController.java ├── entity │ └── SysUser.java ├── mapper │ ├── SysUserMapper.java │ └── SysUserMapper.xml ├── service │ ├── SysUserService.java │ └── impl │ └── SysUserServiceImpl.java第一次打开工程目录时你可能觉得东西多了点但每个文件都很规矩。我先建议你做的事是通读一遍SysUser实体类确认字段注解和数据库对得上再去看SysUserMapper.xml确认 resultMap 没有因为字段类型映射出明显的坑比如数据库的text类型映射成String这种。3.2 entity 层注解别乱删生成字段别乱加实体类生成之后有几个注解是“保命”的TableName(sys_user)如果表名和类名不一致必须靠这个注解绑定。TableId(value id, type IdType.AUTO)主键自增策略如果你用的是分布式 ID雪花 ID要手动改成IdType.ASSIGN_ID。TableField(create_time)驼峰和下划线之间的桥乱删会导致字段映射失败。TableLogic逻辑删除字段注意它不会被 TableField 自动识别必须生成后手动加或者你在生成策略里配置逻辑删除字段名。这里有个小细节逻辑删除字段的注解MybatisPlus 默认的GlobalConfig中配置了logicDeleteField时会自动加但很多团队没配所以生成完记得检查deleted字段上有没有TableLogic。我见过不止一个同事因为没加这个注解删数据直接硬删历史记录全没了。3.3 mapper 层BaseMapper 够用但别把 XML 当摆设生成的 Mapper 接口继承了BaseMapperT自带selectById、insert、deleteById、updateById和selectList这些方法。简单单表操作直接调基础方法一说就能用。但项目做大了多表 join、复杂 where 逻辑最终还是回到 XML 里手写 SQL。这个时候生成的SysUserMapper.xml就派上用场了——它已经把 resultMap 和基础列名都定义好了你只需要在下面加 SQL 片段不用从零手写。我建议大家对生成的 XML 采取“只增不改”原则不动它预置的 resultMap 和 columns 片段只往里面追加自定义 SQL。这样就算以后重跑生成器覆盖了文件你加的东西虽然会被冲掉至少 base 部分不会出乱子。3.4 service 层IService 和 ServiceImpl 的价值代码生成器默认生成的 Service 接口继承了IServiceT实现类继承了ServiceImplM, T。这两个父类其实自带了很多批量操作方法比如saveBatch、listByIds、lambdaQuery、page等。我的习惯是简单业务直接在 Controller 里调 service 的继承方法复杂业务在 ServiceImpl 里自行实现——因为ServiceImpl已经注入了baseMapper你可以在实现类里直接用baseMapper.selectPage之类的方法不用再通过Resource去注入一遍 Mapper。但要注意ServiceImpl里的事务方法要用Transactional标注。生成器不会自动帮你加事务注解如果你在 service 层写了涉及多张表的写操作一定要记得加。我见过一个线上问题一条数据更新掉了一半另一半报错回滚不了就是因为 service 方法没加事务。3.5 controller 层生成的标准接口只能当脚手架生成器生成的 Controller 默认带以下接口GET /sysUser/page 分页查询 GET /sysUser/{id} 按ID查询 POST /sysUser 新增 PUT /sysUser/{id} 修改 DELETE /sysUser/{id} 按ID删除这套接口是标准的但它不会考虑你的权限控制、参数校验、返回体格式。建议的改造点是把返回类型从Result改成你们系统的RT统一返回体。加入Validated参数校验注解比如新增时name不能为空、email 格式要正确。分页接口要加ApiOperation注释方便生成 Swagger 文档。删除接口建议改成逻辑删除或者批量删除避免直接物理删除导致数据不可恢复。4. 避坑实录一分页失效的 5 个典型原因4.1 插件没装配分页参数被当成普通参数社区里问“mybatisplus分页失效”的帖子十有八九是忘了配置PaginationInnerInterceptor。我最初也栽在这里——数据库里只有 20 条数据Page 传了 size10结果查出来 20 条明显是插件没生效。MybatisPlus 3.5.x 的配置方式是这样的Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置类有了你的 Mapper 方法签名也必须按规范来分页参数IPageT要放在第一个参数位置IPageSysUser selectUserPage(IPageSysUser page, Param(name) String name);只写ListSysUser作为返回类型分页照样失效因为插件只对返回IPage或参数带IPage的方法做拦截处理返回 List 时它不会帮你执行 limit 包装。4.2 Page 对象传错位置参数没进 SQL就算你配好了插件还有一类低级错误特别容易犯——Page 对象传错了位置。比如 XML 里写了select idselectUserPage resultTypecom.example.demo.entity.SysUser SELECT * FROM sys_user WHERE name #{name} /selectMapper 方法却是IPageSysUser selectUserPage(Param(name) String name, IPageSysUser page);这种情况下如果 XML 里的 #{name} 用的是普通参数Page 放在第二位也不会生效——MybatisPlus 插件要求IPage参数要么是第一个参数要么 XML 里按#{page.xxx}访问分页对象否则插件无法识别。我的建议是固定一个规范所有自定义分页查询方法第一参数都放IPageT返回类型都用IPageT。这样既满足插件的要求团队代码也能保持统一不会有人换着花样写。4.3 自定义 SQL 把分页参数写在 SQL 里了还有一种情况你自己在 XML 里写了LIMIT #{pageSize}然后传了 Page 对象又传了 pageSize。插件发现 SQL 里已经带了 limit 关键字就不会再追加 limit 了这时候你传的 Page 参数会被人为忽略或者和 SQL 里的 limit 冲突。正确做法是自定义 SQL 里不写 limit让插件来帮你拼。MybatisPlus 的PaginationInnerInterceptor会自动改写 SQL在原有 SQL 后面追加 limit 参数你只需要把 Page 对象传进去即可。如果你自己写了 limit等于抢了插件的活还做得不完整。4.4 多数据源项目里的漏配多数据源是分页失效的重灾区。一个工程里同时配了 MySQL 和 Oracle但你只在某个SqlSessionFactory里加了分页插件另一个没加那么走另一个数据源的查询自然就没有分页效果。而且多数据源还有个坑分页插件的DbType必须和数据源匹配。MySQL 的方言和 Oracle 的方言不同配错了DbType虽然不会报错但生成的 SQL 可能不兼容比如在 Oracle 上生成 MySQL 的 limit 语法直接 SQL 报错。排查多数据源分页问题我建议大家先确认当前请求走的是哪个数据源再去看对应的SqlSessionFactory是否装配了插件。别一上来就怀疑代码先把环境因素排除掉。4.5 分页失效排查速查表排查点检查内容处理方式插件配置MybatisPlusInterceptor 是否定义补充 PaginationInnerInterceptor方法签名IPage 参数是否在第一位调整参数顺序返回类型方法是否返回 IPage改为 IPage 返回XML 写法SQL 是否手写了 limit删除 SQL 中的 limit数据源当前数据源是否配了插件检查多数据源配置DbType插件配置的数据库类型改成对应数据库类型5. 避坑实录二Irepository 这个新玩法到底怎么用5.1 Irepository 是什么为什么 MybatisPlus 要出这个接口如果你用的是 MybatisPlus 3.5.4 以上的版本会发现代码生成器生成的 service 接口可以不再只继承IServiceT而是可以选择继承一个新的IRepositoryT。Irepository 的设计思路借鉴了 Spring Data JPA 的 Repository 风格把一些通用查询方法收敛到基础接口里。它的定位是当你不想让 service 层暴露出 IService 那一大堆方法时用 Irepository 做一个更精简的“只读/轻写”接口只暴露你真正用到的几个方法。举个例子以前 service 接口继承 IService 后list、getById、saveOrUpdate这些方法全部对外暴露controller 层想调什么就调什么。但有些业务场景你希望接口只允许findById和findByName不希望别人随便saveOrUpdate覆盖数据。用 Irepository你可以自定义暴露哪些查询方法把写操作封在实现类内部。5.2 Irepository 的基本用法与扩展方式先看生成代码时的配置。在strategyConfig里加一行.serviceBuilder().enableIrepository()生成的 service 接口会变成public interface SysUserRepository extends IRepositorySysUser, Long { // 这里可以自定义查询方法 ListSysUser findByName(String name); }实现类则继承RepositoryImplService public class SysUserRepositoryImpl extends RepositoryImplSysUser, Long implements SysUserRepository { Override public ListSysUser findByName(String name) { return lambdaQuery().eq(SysUser::getName, name).list(); } }如果你只是想用 Iservice 的大量现成方法比如page、saveBatch那么建议继续用 Iservice如果你的 service 对外只暴露几个查询方法内部写操作不想被 controller 直接调用用 Irepository 更干净。5.3 什么时候别用 IrepositoryIrepository 不是银弹我不建议在核心业务系统里大规模切换。原因有几点它是较新的接口团队里大多数人还没用过容易产生理解偏差。有些同事已经习惯了IService的lambdaQuery链式写法换到IRepository后思维转换成本高。如果你后续要接一些第三方组件比如 Sa-Token 或 Spring Security 的权限数据源它们默认适配的还是IService/ServiceImpl那套体系用 Irepository 反而要写适配层。我的建议是新项目可以试用小规模模块跑顺了再推广存量项目不用刻意迁移除非你正好在重构某个低风险模块。6. 避坑实录三单页 500 条限制的真相与解除6.1 500 条限制到底从哪来“接触 MybatisPlus 单页 500 条限制”这个说法实际对应的是 MybatisPlus 分页插件里的maxLimit参数。在老版本或者某些配置残缺的版本里分页插件默认给Page设置了一个单页最大条数超过就自动截断成 500。这是分页插件为了防有人一次性拉全表默认加的一层保护但很多场景下我们确实需要翻页拉出超过 500 条的数据比如导出、批量同步。当你的代码page(new Page(1, 1000))查不出来 1000 条时别急着怀疑 SQL 写错了先检查一下maxLimit是否生效。6.2 maxLimit 配置与解除方案MybatisPlus 3.5.x 配置分页插件时可以显式设置单页最大限制PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(2000L); // 单页最大 2000 条 pagination.setOverflow(false); interceptor.addInnerInterceptor(pagination);解除限制的方式有两种一种是把maxLimit设成-1L表示不限制单页条数。适合内部分页查询不受控但数据量可控的场景。另一种是不要直接在插件上卡 total 条数而是在业务层根据需求给Page设 size并对超大 size 做业务拦截判断比如导出接口允许大分页其他接口限制单页不超过 100。我个人的建议是线上接口用保守值比如 100~200防止有人恶意翻页拉数据后台导出任务或批处理场景单独走一个不带maxLimit限制的查询入口。这样既解决了 500 条限制又不会把整个接口的口子敞开。6.3 数据库方言下的分页边界MybatisPlus 分页插件在底层会按数据库方言生成不同的分页 SQL。MySQL 用LIMIT offset, sizeOracle 用ROWNUM或OFFSET FETCHSQL Server 用OFFSET FETCH。数据库不同单页条数的限制表现也不一样。Oracle 老版本 rownum 有个经典问题ROWNUM 是在结果集生成前指定的超过一定量级的查询容易触发 ORA-01427 或者性能急剧下降。这时候就算你解除 MybatisPlus 的 maxLimit数据库本身的限制也没办法靠 Java 层突破需要优化 SQL 或者改成分页游标方式比如分批拉取。另一个容易被忽略的点是MySQL 里LIMIT 1000000, 20这种写法虽然能查到数据但 MySQL 需要扫过前 100 万行才能返回结果性能非常难看。所以大偏移量分页最好是结合where id ?这种游标方式而不是一页一页往后翻。6.4 解除限制之后的性能与安全考虑解除“单页 500 条限制”不是无脑改配置。我处理过的几个真实案例里放开限制后最常遇到的问题有三个一是接口响应超时。大分页一次拉几千条数据库执行慢前端接口等不起。解决思路是做异步导出而不是同步返回大数据。二是内存撑爆。几千条数据从数据库取出来后如果还要做对象转换、关联字段拼装内存占用会成倍增长。建议在 service 层分页批量处理每次只处理 500 条循环搞定后再聚合。三是接口权限问题。一般分页接口是对外开放的如果你把单页条数放开到几千甚至上万一旦有人写脚本循环翻页等于不设防地把你整张表的数据全拉走。所以我强烈建议对外接口的 maxLimit 保持保守内部同步/导出的代码单独开一个受限接口。7. 常见问题速查表问题可能原因解决办法生成器报找不到驱动pom 里缺 mysql-connector加依赖并确认 scope 为 runtime生成的实体没有 TableLogic未配置逻辑删除字段在策略里配置 logicDeleteFieldservice 接口生成后编译报错已存在同名接口检查同包下是否有旧文件mapper XML 不生效XML 不在 resources 目录检查 mapper-locations 配置分页查询返回 total0 或 total 不准count 子查询有问题自定义 count 查询或检查 SQL分页 size 超过 500 被截断maxLimit 未设置显式设置 maxLimit 为所需值Irepository 生成后不识别版本低于 3.5.4升级 MybatisPlus 版本生成的 controller 接口无权限生成器不生成鉴权注解手动加权限注解写在最后的实操心得这篇文章里写的每个坑基本都能对应到我自己或同事在真实项目里摔过的跟头。代码生成器本身只是工具它真正改变的是团队规范和执行效率统一生成、统一风格、减少低水平重复劳动。但工具用不好该失效的分页还会失效该被截断的数据还是会被截断Irepository 也不会自动帮你把代码变优雅。我个人比较推荐的落地方式是新项目开局就把生成器配好跑通一张表作为基线然后全团队照这个基线生成同时把分页插件、逻辑删除、乐观锁这些全局配置一次性配好后面就少很多“为什么别人项目没这个问题”的疑惑。如果你现在正准备接入或者已经接入但被分页、500 条限制这类问题卡住可以照着上面的配置和排查表走一遍比到处搜答案强得多。最后再分享一个小技巧生成器跑完代码后建议你第一时间跑一遍分页查询的单元测试确认插件生效、total 统计正确、数据能正常返回。这一步花不了十分钟但能把你从“半夜上线发现分页接口全吐全表”的危机里救出来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价