☰
Spring Boot 3 整合 MyBatis-Plus:boot3 starter 与版本兼容避坑指南
2026/10/26 10:49:56 网站建设 项目流程

简介:这是一份面向Java后端开发者与Spring Boot初学者的整合示例工程,围绕Spring Boot 3与MyBatis-Plus的协同开发展开,适合需要参考版本搭配、数据源配置、实体映射及基础CRUD分层写法的读者。压缩包共24个文件、约85KB,以7个Java源码和7个class文件为主体,辅以yaml与properties配置、pom.xml构建文件、jar依赖、说明文档及Maven包装脚本,覆盖实体类、Mapper、Service、Controller等典型MVC项目结构,并保留测试入口与基础配置示例,便于核对依赖版本、包扫描路径和数据库连接参数。已有1435人学习下载,具备一定参考热度。包内提供可导入运行的工程骨架,便于查看自动配置、MyBatis-Plus注解映射、BaseMapper通用CRUD与QueryWrapper条件构造器的实际落地方式,也可作为课程设计、练习项目或整合排错时的对照样本,帮助读者快速理解该技术组合的项目组织与配置要点。

1. 从一次启动失败说起:坐标里少了个 boot3

上个月帮人接一个 Spring Boot 3 + MyBatis-Plus 整合的工程,pom.xml是照着两年前的教程抄的,mvn spring-boot:run一跑,控制台第一行就红:Invalid value type for attribute 'factoryBeanObjectType'。翻来覆去查依赖,最后发现是 artifactId 写成了mybatis-plus-boot-starter——那是给 Spring Boot 2 用的坐标,Spring Boot 3 要的是带boot3后缀的那个。这种坑不玄学,纯粹是版本没对齐。整合这件事本身不难,难在 Boot 3 把 JDK 底线抬到了 17、把javax.*全换成了jakarta.*、把自动配置的注册文件也换了位置,三件事叠在一起,任何一处沿用了老写法都会在启动阶段翻车。这篇记录写给手上已经有 Boot 3 工程、要往里接 ORM 的人,也写给从 Boot 2 升上来、卡在 starter 坐标上的同学。

2. 环境清单:JDK 17、Maven 3.9 与 MyBatis-Plus 3.5.7 的搭配

动手之前先把机器上的东西列清楚。整合失败十次里有六次不是代码写错,是版本组合本身不成立——比如 JDK 用 11 去跑 Spring Boot 3,编译能过,启动直接告诉你 class file version 不对;再比如 MyBatis-Plus 停在 3.5.3,mybatis-spring还在 2.x,跟 Spring Framework 6.1 的 FactoryBean 判定对不上。先把这条链条钉死,后面每一步才有意义。

2.1 三个版本的硬约束

Spring Boot 3 的基线是 Java 17,这不是建议是硬性要求,官方在 3.0 就把最低 JDK 提到了 17。所以机器上必须装 JDK 17 或更高(21 也稳),并且JAVA_HOME指向它。常见做法是本地留多个 JDK,用update-alternatives或者在 IDE 里单独给这个工程配 SDK,别去动全局默认,不然别的老项目会跟着遭殃。

构建工具用 Maven,版本别低于 3.6.3,我一般直接上 3.9.x。低版本 Maven 在解析 Spring Boot 3 的 BOM 时偶尔会出现依赖树解析异常,尤其是用了dependencyManagement又混着私服镜像的场景。

MyBatis-Plus 这边,3.5.5 是一个分水岭:从这个版本起,官方提供了专门适配 Spring Boot 3 的 starter,内部依赖的mybatis-spring也升到了 3.0.3 以上,正好绕开前面提到的 FactoryBean 报错。我这次用的是 3.5.7,实际选型时 3.5.5 及以上都可以,低于这个数就得自己手动压mybatis-spring的版本,属于给自己找事。

2.2 一张对照表把版本钉死

组件本次使用版本最低要求版本不对的典型表现
JDK17.0.x17启动报Unsupported class file major version
Maven3.9.63.6.3BOM 解析异常、依赖下载不全
Spring Boot3.2.53.2.x 起3.0/3.1 也能跑,但部分自动配置类名有差异
MyBatis-Plus3.5.73.5.5低于 3.5.5 需手动升mybatis-spring
MySQL8.0.x8.0驱动坐标在 Boot 3 里已改成mysql-connector-j
连接池HikariCP(Boot 内置)无需单独引手引 Druid 时要改配置前缀

表里 Spring Boot 那一行需要多说一句。3.2.x 和 3.3.x 在整合层面没有本质差别,差异主要在可观测性和部分 starter 的默认行为上。如果你的工程是从 3.0 升上来的,建议直接跳到 3.2.5 这样的稳定小版本,别在 3.0.0 上磨。

2.3 装之前先跑一遍环境自检

三条命令,三十秒,能省掉后面半小时的猜谜。

# 确认 JDK 版本,输出必须是 17 或以上 java -version # 确认 Maven 版本与它实际使用的 JDK mvn -v # 确认 3306 端口上有 MySQL 在监听 # Linux/macOS 用 nc,Windows 用 telnet 或直接连客户端试 nc -zv 127.0.0.1 3306

mvn -v的输出里有一行Java version,这一行比java -version更关键——很多人机器上java -version显示 17,但 Maven 走的是自己conf/wrapper里配的 JDK 8,编译期看不出问题,运行期直接崩。看到这两个数字不一致,先去改JAVA_HOME或者 Maven 的settings.xml,别往下走。

nc那条如果连不上,先别怀疑驱动,去看 MySQL 服务有没有起来、账号有没有开远程访问权限。整合阶段报的「连接被拒绝」,八成跟代码无关。

3. 依赖落地:pom.xml 里那个带 boot3 的 starter

版本确认完,接下来是把依赖写对。这一章的核心只有一个问题:到底该引哪个 artifactId。答案取决于你的 Spring Boot 大版本,跟 MyBatis-Plus 的版本号无关,这一点很多人会搞混。

3.1 父工程锁定 Spring Boot 3.2.5

用spring-boot-starter-parent做父工程是最省心的做法,它把几百个依赖的版本都管住了,后面引 MySQL 驱动、测试 starter 都不用写 version。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <properties> <!-- 编译目标,必须是 17 及以上 --> <java.version>17</java.version> <!-- 把 MyBatis-Plus 版本提到 properties 里,方便统一改 --> <mybatis-plus.version>3.5.7</mybatis-plus.version> </properties>

<relativePath/>这个空标签别漏,它的作用是告诉 Maven 别去上级目录找父 pom,直接从仓库拉。漏了在多层目录结构下会报「Non-resolvable parent POM」,这个报错跟网络、私服一点关系都没有,纯粹是路径找错了。

java.version这个属性会被 Spring Boot 的父 pom 读取,自动设置maven.compiler.source和target。写 17 就行,别画蛇添足再单独配一遍编译器插件,两处不一致反而容易出问题。

3.2 两个 starter 坐标差在哪

这是整篇最关键的一张对照,抄错一个字母,启动就崩。

<dependencies> <!-- Web 场景,顺带把 Jackson、Tomcat 都带进来 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 关键:Spring Boot 3 必须用这个带 boot3 的坐标 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Boot 3 起 MySQL 驱动的 groupId 变成了 com.mysql --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

三个点必须说明白。

第一,mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter的区别不在功能,而在自动配置的注册方式。Spring Boot 2 用META-INF/spring.factories,Spring Boot 3 改成了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。旧坐标里的注册文件是前者,Boot 3 压根不读,结果就是依赖引进来了、Bean 一个没注册,启动时报找不到SqlSessionFactory,看着像依赖没下下来,其实是自动配置没生效。

第二,MySQL 驱动的 groupId 从mysql:mysql-connector-java换成了com.mysql:mysql-connector-j。旧坐标在 Boot 3 的 BOM 里已经不管版本了,引了就得自己写 version,写高了写低了自己担着。用新坐标,版本由父工程管。

第三,mybatis-spring和mybatis这两个传递依赖不要自己去覆盖版本。我见过有人在dependencyManagement里把mybatis-spring锁到 2.0.7,理由是「另一篇文章说这样能解决兼容问题」,结果是 Boot 3 下必崩。3.5.5 以上的 MyBatis-Plus 已经把这两个依赖调到合适的位置,手动干预只会破坏平衡。

3.3 编译与打包插件的两个细节

Lombok 在 JDK 17 上要配注解处理器,否则 IDE 里一片红但命令行能编过,这种「IDE 报错、Maven 正常」的分裂状态最消耗耐心。pom.xml里加一条:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

<optional>true</optional>的作用是阻止 Lombok 传递给下游模块,它只在编译期有用,不该出现在运行时依赖里。同时别忘了在 IDE 设置里打开 Annotation Processing,IDEA 是Settings → Build → Compiler → Annotation Processors → Enable。

打包插件这块,Spring Boot 3 的spring-boot-maven-plugin配置里有个<excludes>,可以把 Lombok 从最终的 fat jar 里剔掉:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>

这个不是必须的,不加也能跑,但打出来的 jar 会白白多几百 KB。上线前顺手做掉,属于成本极低收益明确的操作。

依赖写完先别急着写代码,跑一条mvn dependency:tree -Dincludes=com.baomidou看一眼依赖树。正常应该看到mybatis-plus-spring-boot3-starter下面挂着mybatis-plus-extension、mybatis-plus-core、mybatis-spring 3.0.x、mybatis 3.5.x。如果看到同一个mybatis-spring出现两个版本,那就是别的地方也引了 MyBatis 相关依赖,这时候才需要<exclusions>出手。

4. 配置写对:数据源、Mapper 扫描与分页拦截器

依赖通了,接下来是配置。这个阶段的问题最隐蔽,因为启动可能不报错,跑起来才发现查出来的字段全是 null,或者分页永远返回全量数据。配置项分三块:数据源、MyBatis-Plus 的全局设置、以及必须手工注册的拦截器和扫描路径。

4.1 数据源:url 参数一个都不能省

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true username: demo_user password: demo_pass hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000

driver-class-name在 Boot 3 里可以不写,Hikari 会自己从 url 推断,但写上更直观,尤其是工程里同时存在多个数据源的时候。

url 后面那串参数是血泪经验。serverTimezone=Asia/Shanghai不写,MySQL 8 在某些时区配置下会报The server time zone value is unrecognized,这个报错信息里看不出跟时区有关,第一次见很容易懵。useSSL=false是本地开发环境省掉握手,生产该开还是得开。allowPublicKeyRetrieval=true配合 MySQL 8 的caching_sha2_password认证插件用,不加这条在某些环境会报Public Key Retrieval is not allowed。rewriteBatchedStatements=true是给批量插入用的,单条查询用不上,但提前加上没坏处。

账号密码这块,本地开发直接写明文,生产环境换成环境变量。写法是把password: demo_pass改成password: ${DB_PASSWORD:},冒号后面留空作为兜底。别在 yml 里提交真实密码,这件事没有商量余地。

Hikari 的五个参数里,maximum-pool-size是最容易拍脑袋定的。10 是个保守值,够本地开发和中小流量的测试环境用。真上生产要看数据库的最大连接数和实际并发,池子开太大反而会把数据库拖垮。connection-timeout设 30 秒,取连接超时的那一刻会抛异常,日志里能看到,比线程无声无息地挂住强。

4.2 mybatis-plus 命名空间下的全局项

mybatis-plus: # XML 映射文件位置,没有 XML 也要留着,避免后面加的时候忘了 mapper-locations: classpath*:/mapper/**/*.xml # 实体类包路径,XML 里写 resultType 时可以省掉全限定名 type-aliases-package: com.example.demo.domain configuration: # 数据库下划线字段自动映射到 Java 驼峰属性 map-underscore-to-camel-case: true # 开发期把 SQL 打到控制台,上线换成 NoLoggingImpl log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查询结果为 null 的字段也返回,前端不用做空判断 call-setters-on-nulls: true global-config: banner: false db-config: # 主键策略:雪花算法,适合分布式;单库自增改成 auto id-type: assign_id # 逻辑删除字段名,全局生效,实体上不用再标注 logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 # 表名统一前缀,没有就删掉这行 table-prefix: t_

逐项说一下取舍。

map-underscore-to-camel-case默认就是 true,但显式写出来更好。一旦项目里有原生 MyBatis 的 XML 混用,这个开关的统一性就变得很重要。

log-impl用StdOutImpl只在开发期合适,它把 SQL 直接打到标准输出,不带参数占位符替换,看的时候得自己对着Parameters:那行拼。生产环境换成org.apache.ibatis.logging.nologging.NoLoggingImpl,或者干脆交给日志框架的 DEBUG 级别管理。我见过有人在生产环境留着 StdOutImpl,日志文件一天涨几个 G,最后磁盘打满,排查了半天才定位到这一行配置。

logic-delete-field是个全局默认值。配了它,实体里只要有deleted这个字段,删除操作就会自动变成UPDATE ... SET deleted=1。这是 MyBatis-Plus 里少有的「配了就一直生效」的开关,好处是省事,坏处是如果有人写了个deleted字段但本意不是逻辑删除,行为会超出预期。团队里要么统一用,要么统一不用,别一半一半。

table-prefix我这次开了,因为示例表都用t_前缀。这里有个坑:开了前缀以后,@TableName注解里的表名会被拼接,如果注解里写的已经是t_user,实际查的就是t_t_user。要么用前缀 + 实体类名,要么关掉前缀全部靠注解,二选一。

4.3 分页拦截器与 Mapper 扫描的注册位置

这两件事都靠配置类完成,缺一个就跑不通。

@Configuration public class MybatisPlusConfig { /** * 分页插件。不注册这个 Bean,selectPage 会退化成全表查询。 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 第一个参数必须和实际数据库类型对上,写错会导致分页 SQL 语法错误 PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL); // 单页最大条数,防止前端传个 pageSize=99999 把库拖垮 pagination.setMaxLimit(500L); // 页码超过总页数时返回空列表,而不是回到第一页 pagination.setOverflow(false); interceptor.addInnerInterceptor(pagination); return interceptor; } }

DbType.MYSQL这个参数看着不起眼,写错了分页 SQL 的方言就不对。比如连的是 PostgreSQL 却写了 MYSQL,生成的LIMIT ?,?语法直接报错。多数据源的项目里尤其要注意,不同数据源要用不同的拦截器实例。

setMaxLimit(500L)是我习惯加的一道保险。分页接口对外暴露,前端传参不做校验的话,一个pageSize=100000就能把数据库连接占满。设了上限以后,超出的部分会被截断到 500,不报错,但也不会出事。

setOverflow(false)控制的是页码越界行为。false 表示第 100 页没数据就返回空列表,true 表示自动回到第一页。这个看业务,列表页一般用 false,用户能明确知道翻到底了。

Mapper 的扫描有两种写法,选一种就行:

// 写法一:启动类上打注解,适合 Mapper 都在同一个包下 @SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
// 写法二:配置类上打注解,适合多个包、需要分组管理 @Configuration @MapperScan(basePackages = "com.example.demo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate") public class MapperScanConfig { }

两种写法效果一样,别同时用。同时用不会报错,但会重复注册,启动日志里能看见警告,属于没必要的噪音。包名写错是最常见的失误,com.example.demo.mapper写成com.example.demo.dao而实际包名是前者,启动直接报No qualifying bean of type 'UserMapper'。

4.4 落一个最小可跑的实体和 Mapper

配置写完,用一个最简单的实体验证整条链路是否打通。

@Data @TableName("t_user") public class User { // ASSIGN_ID 走雪花算法,插入时自动生成 19 位 Long @TableId(type = IdType.ASSIGN_ID) private Long id; // 数据库字段是 user_name,靠驼峰映射自动对应 private String userName; private Integer age; // 配合全局 logic-delete-field 使用,这里不必再写 @TableLogic private Integer deleted; }
// 继承 BaseMapper 就自动拥有 selectById、insert、selectPage 等方法 public interface UserMapper extends BaseMapper<User> { }

@TableId(type = IdType.ASSIGN_ID)里的雪花算法生成的是 Long 型 ID,前端接收时要注意 JavaScript 的 Number 精度只能安全表示到 2^53,19 位的 ID 直接返回会丢精度。常见做法是在序列化时把 Long 转成 String,加个@JsonSerialize(using = ToStringSerializer.class)就行。这个坑跟整合本身无关,但一定会遇到。

实体字段名和数据库列名的对应关系是:userName去掉驼峰变成user_name,再拼上table-prefix得到表名。如果表名不符合这个规则,就老老实实写@TableName("实际表名"),别跟约定较劲。

写完实体和 Mapper,先用mvn compile编一遍。编译通过说明依赖链没问题,接下来才是运行时验证的事。

5. 常见问题排查:五个让整合卡在半路的坑

这一段全是踩出来的。每一条都按「看到什么、为什么、怎么改」写,照着对号入座就行。

5.1 启动报 Invalid value type for attribute 'factoryBeanObjectType'

现象:应用启动,控制台抛出IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String,堆栈指向 Spring 的DefaultListableBeanFactory,看不出任何 MyBatis 的影子。

原因:这是 Spring Framework 6.1(对应 Spring Boot 3.2)收紧了FactoryBean属性类型的判定,而mybatis-spring3.0.2 及以下版本还在用旧写法往这个属性塞字符串。两者对不上就炸。MyBatis-Plus 3.5.5 以下带的是老版本mybatis-spring,所以正好中招。

解决:首选把 MyBatis-Plus 升到 3.5.5 及以上。如果因为其他依赖钉死不能升,就在dependencyManagement里显式把mybatis-spring提到 3.0.3:

<dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.3</version> </dependency> </dependencies> </dependencyManagement>

改完跑mvn dependency:tree -Dincludes=org.mybatis:mybatis-spring确认只有一个版本生效。看到两个版本共存说明还有别的地方在引,得往上找。

5.2 Mapper 注入报 No qualifying bean

现象:@Autowired private UserMapper userMapper;编译能过,启动时报No qualifying bean of type 'com.example.demo.mapper.UserMapper' available。

原因:三种可能。一是@MapperScan完全没加;二是包路径写错,扫的包和 Mapper 实际所在的包对不上;三是 Mapper 接口上没有@Mapper注解,同时@MapperScan也没覆盖到。

解决:先确认@MapperScan的值和 Mapper 的实际包名逐字符一致。IDE 里按住 Ctrl 点一下注解里的字符串,能跳转到包才算对。如果 Mapper 分散在多个包,用basePackages = {"com.example.demo.mapper", "com.example.demo.other.mapper"}数组形式。实在懒得管,就在每个 Mapper 接口上补@Mapper,二选一,别都不做。

5.3 分页查询返回总数 0 或者 SQL 里没有 LIMIT

现象:调用userMapper.selectPage(new Page<>(1, 10), null),返回的total是 0,或者拿到的数据是全表,SQL 日志里只有SELECT ... FROM t_user没有LIMIT。

原因:MybatisPlusInterceptor没注册,或者注册了但没往里加PaginationInnerInterceptor。另一个可能是拦截器 Bean 所在的包没被 Spring 扫到,比如配置类放在了启动类同级之外的目录。

解决:确认配置类上有@Configuration,mybatisPlusInterceptor()方法上有@Bean,并且方法体里调用过interceptor.addInnerInterceptor(pagination)。注册成功后,启动日志里会看到拦截器被加载,SQL 日志里分页查询会先执行一条SELECT COUNT(*),再执行带LIMIT的查询。看不到 COUNT 那条,就是没生效。

5.4 delete 把数据真删了

现象:调userMapper.deleteById(1L),日志里打的是DELETE FROM t_user WHERE id=?,数据库里的记录直接没了,而不是把deleted置 1。

原因:逻辑删除的配置没生效。要么全局的logic-delete-field没配,要么配了但实体里的字段名不叫deleted,要么字段类型不是整型(逻辑删除字段一般是Integer或Boolean,用String需要额外指定删除值)。

解决:在application.yml的mybatis-plus.global-config.db-config下补上logic-delete-field、logic-delete-value、logic-not-delete-value三项,或者在实体字段上直接标@TableLogic。改完重启,日志里的DELETE会变成UPDATE t_user SET deleted=1 WHERE id=? AND deleted=0。注意后面的AND deleted=0,这是防止重复删除的关键,别在 SQL 里手写 delete 语句绕过它。

5.5 启动报 Failed to configure a DataSource

现象:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured。

原因:Spring Boot 在类路径上发现了数据源相关的类,但没找到spring.datasource.url。常见于 yml 缩进错位,url写成了datasource的兄弟节点而不是子节点;或者工程里引了 Druid,配置前缀要从spring.datasource换成spring.datasource.druid,两边混着写就有一边读不到。

解决:用 IDE 的 YAML 插件看缩进层级,或者临时在启动类里打印Environment的spring.datasource.url值。如果值存在但启动还是报这个错,检查是不是有多个配置文件(application.yml和application-dev.yml)冲突,profiles 没激活对。

6. 验证跑通:从启动日志到一次真实的分页查询

配置写完不等于跑通,得有一组能复现的验证动作。我一般按三层来:先看启动日志,再跑单元测试,最后发一次真实 HTTP 请求。

第一层看日志。启动成功的标志有四行:HikariPool-1 - Start completed、Tomcat started on port(s): 8080、Started DemoApplication in x.xxx seconds,以及 MyBatis-Plus 打的MybatisPlusInterceptor加载信息。缺哪一行就知道卡在哪。看不到 HikariPool,是数据源没配上;看不到 Tomcat,是 web starter 没引。

第二层跑单测。把@SpringBootTest挂上,直接调 Mapper:

@SpringBootTest class UserMapperTest { @Autowired private UserMapper userMapper; @Test void pageQueryShouldReturnLimitSql() { // 第二页,每页 5 条 Page<User> page = new Page<>(2, 5); Page<User> result = userMapper.selectPage(page, null); // 控制台应能看到 SELECT COUNT(*) 和带 LIMIT 5,5 的查询 System.out.println("total=" + result.getTotal()); System.out.println("records=" + result.getRecords().size()); } }

跑起来以后盯控制台。应该先出现一条SELECT COUNT(*) AS total FROM t_user,再出现一条带LIMIT ?,?的查询,Parameters:那行里能看到5,5。这两条都对齐,说明分页拦截器和数据源全部通了。total返回 0 而表里明明有数据,回头查逻辑删除字段——deleted为 null 的记录会被过滤掉,这是最容易忽略的一点。

第三层发一次真实请求。写个最小 Controller,浏览器或者 curl 打一下:

curl -s "http://127.0.0.1:8080/user/page?current=1&size=5" | head -c 500

返回的 JSON 里应该有records、total、size、current四个字段。records为空数组但total不为 0,说明分页参数没传到 Service 层;直接 500,去看控制台堆栈,多半是字段映射问题,比如数据库列是user_name但实体属性叫username,驼峰映射对不上。

把这三层当成固定动作,我习惯在每次改完数据源或者升级 MyBatis-Plus 版本后强制走一遍。上个月就是因为跳过了单测那步,直接联调,结果分页插件没注册,前端列表页一次拉了两万条,接口超时被网关掐断,排查了一个下午才回到配置类上加那三行。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询