ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot 3.3整合MyBatis-Plus:版本选型、分页逻辑删除与XML配置指南

Spring Boot 3.3整合MyBatis-Plus:版本选型、分页逻辑删除与XML配置指南 Spring Boot 3.3.X 发布到现在终于算是进入了多数团队敢在正式环境里用的阶段。我把自己手头几个还在 Spring Boot 2.7 的老项目逐个往上搬最在意的倒不是框架本身而是 MyBatis-Plus 的兼容性。这篇就专门聊 Spring Boot 3.3.X 整合 MyBatis-Plus从版本选型到最小工程从分页、逻辑删除这类常用配置到网上问得最多的“XML 和 Mapper 放同一个文件夹怎么配”最后顺手把 Spring Data JPA 和 MyBatis-Plus 的区别也讲透。内容偏实操照着做基本能一次跑通。1. 版本选型与工程准备1.1 为什么 Spring Boot 3.3.X 和 MyBatis-Plus 之间要特意谈版本Spring Boot 3.x 和 2.x 最大的差别是基础框架换成了 Spring Framework 6并且强制要求 Java 17 起步。MyBatis-Plus 老版本里的mybatis-plus-boot-starter是基于 Spring Boot 2 的自动装配机制写的直接拿来用到 3.3.X 上可能出现自动配置不生效、创建 Mapper Bean 失败、启动直接抛异常这类问题。所以从 Spring Boot 3.0 开始MyBatis-Plus 官方单独提供了mybatis-plus-spring-boot3-starter这个 starter。我在 3.3.X 项目里使用的是3.5.7版本兼容性稳定官方后续版本也一直在维护。如果你当前项目的 Spring Boot 版本是 3.3.x建议优先选 3.5.7 或更新的 3.5.x尽量避免用 3.5.4 以前的版本减少踩坑。注意如果你的 pom 里同时引入了mybatis-plus-boot-starter和mybatis-plus-spring-boot3-starter会导致类冲突。保留 boot3 这个即可另一个要彻底移除。1.2 初始化工程时我推荐的做法创建 Spring Boot 3.3.X 工程我一般直接用 Spring InitializrJava 选 17 或 21打包方式随意但建议用 Jar。依赖暂时不选太多MyBatis-Plus 相关依赖后续手动加这样能更清楚每个依赖的作用。数据库这边我用的是 MySQL 8.x驱动依赖建议用官方com.mysql:mysql-connector-jSpring Boot 3.3 的依赖管理里已经默认管理了它的版本不需要再写version。连接池就选 HikariCPSpring Boot 默认就是它不用额外配置什么花活。目录结构上我习惯按业务模块分包比如controller、service、mapper、entity。mapper接口和 XML 的关系后面单独说这里先强调一点包路径要清晰最好从com.公司名.项目名开始避免后面MapperScan扫描的时候出现各种诡异问题。2. 最小可运行整合依赖、配置、第一个 Mapper2.1 依赖到底该引哪几个一个最小可运行的工程pom 里只需要三块依赖spring-boot-starter-web提供 Web 能力。mybatis-plus-spring-boot3-starter版本 3.5.7。mysql-connector-j运行时由 Spring Boot 管理版本。核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency这里特别说明一下为什么不是引入mybatis-plus-boot-starter。网上很多老教程还是让加旧的 starter在 Spring Boot 2.x 时代没问题但放到 3.3.X 下就是给自己找麻烦。Spring Boot 3 的自动配置机制改了新 starter 专门做了适配用新的方案是成本最低的。2.2 application.yml 配置与数据源准备在src/main/resources/application.yml里先配好数据源再加 MyBatis-Plus 的基本配置。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: false db-config: id-type: assign_idmap-underscore-to-camel-case一定要开数据库字段create_time才能自动映射到实体属性createTime。log-impl配成StdOutImpl是为了开发阶段能看到 SQL上线前记得去掉或改成别的实现。mapper-locations这里的写法是常规的resources/mapper方案具体在第四章展开讲。2.3 写一个最基础的用户表 CRUD先建一张简单用户表CREATE TABLE user ( id bigint NOT NULL, name varchar(50) DEFAULT NULL, age int DEFAULT NULL, email varchar(100) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实体类Data TableName(user) public class User { TableId(type IdType.ASSIGN_ID) private Long id; private String name; private Integer age; private String email; }Mapper 接口public interface UserMapper extends BaseMapperUser { }启动类上加扫描SpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }BaseMapperT是 MyBatis-Plus 的核心里面已经内置了insert、deleteById、updateById、selectById、selectPage等常用方法我平时 80% 的单表操作根本不用自己写 SQL。2.4 启动验证与第一个请求写个简单接口验证RestController RequestMapping(/user) public class UserController { Resource private UserMapper userMapper; PostMapping public User add(RequestBody User user) { userMapper.insert(user); return user; } GetMapping(/{id}) public User get(PathVariable Long id) { return userMapper.selectById(id); } }启动项目调用POST /user插入一条数据再调用GET /user/{id}查询。如果日志里能看到 SQL并且能查回数据说明 Spring Boot 3.3.X 整合 MyBatis-Plus 的最小闭环已经跑通了。这一步跑通后后面那些高级功能都是在这个基础上叠加。3. 真正干活时必配的四个功能3.1 分页插件不配置等于分页查询失效MyBatis-Plus 的分页必须依赖拦截器才能生效。如果只写selectPage不注册拦截器SQL 执行时会发现分页参数根本不起作用甚至直接查全表。这个问题在群里被问过无数次排查半天基本都是忘了配置。分页配置类Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }使用示例PageUser page new Page(1, 10); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.orderByDesc(User::getCreateTime); PageUser result userMapper.selectPage(page, wrapper); long total result.getTotal(); ListUser records result.getRecords();为什么用LambdaQueryWrapper而不是普通QueryWrapper因为字符串写死的字段名一旦实体重命名报错要等到运行时LambdaQueryWrapper是类型安全的编译期就能发现问题。我在代码评审时看到QueryWrapper会要求改成 Lambda 写法。3.2 逻辑删除不是真的删数据后台管理类项目基本都要求逻辑删除。MyBatis-Plus 的TableLogic能在执行delete方法时自动把 SQL 转成update非常方便。实体类字段TableLogic TableField(fill FieldFill.INSERT) private Integer deleted;yml 里可以配置删除和未删除的值mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0注意逻辑删除和数据库唯一索引经常冲突。假设业务要求同一个邮箱只能注册一次逻辑删除后字段还在表里唯一索引就会拦住第二次注册。我实际处理方案是删除时把邮箱拼上时间戳或者单独建一张回收站表把历史数据移过去。不要因为逻辑删除省事就忽略这个坑。3.3 字段自动填充创建时间、更新时间不用每次手动 set很多表都有create_time、update_time这类字段每次插入和更新都手动赋值很啰嗦。MyBatis-Plus 提供MetaObjectHandler自动填充机制。实体字段TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;实现类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()); } }这里有个细节很多人不知道直接调用updateById时自动填充生效但用update(Wrapper, entity)这种带条件更新的方式自动填充不一定触发。原因是Wrapper更新时 MyBatis-Plus 无法确定是否走实体填充逻辑。所以我一般强制要求更新必须传实体哪怕实体里面只有主键和要改的字段这样填充才可控。3.4 主键策略与 Long 型 ID 的前端精度问题MyBatis-Plus 默认主键策略是雪花算法生成的 Long 型 ID 超过 JavaScript 安全整数范围前端拿到以后会出现末位四舍五入变成 0 的经典问题。解决方式一般两种一是把主键改成IdType.AUTO依赖数据库自增。单机小项目没问题分库分表场景不推荐。二是在后端做序列化处理把 Long 转成字符串返回前端。我更推荐这个因为能保留雪花 ID 的优势。Bean public Jackson2ObjectMapperBuilderCustomizer longToStringCustomizer() { return builder - builder .serializerByType(Long.class, ToStringSerializer.instance) .serializerByType(Long.TYPE, ToStringSerializer.instance); }加了这个配置后接口返回 JSON 时Long 字段会自动变成字符串。别再问为什么前端拿到的 ID 最后几位是 0多半是这个原因。4. 热搜问题XML 与 Mapper 放同一个文件夹该怎么配置4.1 为什么会找不到 XML很多人的习惯是 Mapper 接口和对应 XML 放在同一个包目录下比如src/main/java/com/example/demo/mapper/UserMapper.java src/main/java/com/example/demo/mapper/UserMapper.xml放到同一个包下看着整齐但问题来了Maven 默认只会把src/main/resources下的文件拷贝到target/classessrc/main/java目录下的普通文件默认不会被打到 classpath 里。所以即使你把 XML 写在了 Java 包目录里构建后target/classes下根本没有这个 XMLMyBatis 启动时自然报Invalid bound statement (not found)。4.2 同目录方案的两种配置如果你坚持要 XML 和 Mapper 同目录那就必须额外配置 Maven 资源过滤把 XML 文件也当资源拷贝。在 pom.xml 的build节点里增加build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources /build同时修改 ymlmybatis-plus: mapper-locations: classpath*:com/example/demo/mapper/*.xml这里classpath*:的作用是扫描所有 classpath 下匹配的路径能避免某些打包场景下只取到第一个路径的问题。加了这个配置后构建出的target/classes里会出现com/example/demo/mapper/UserMapper.xml和 Mapper 接口包路径保持一致就能正常加载了。这是可行的但我不太推荐。4.3 我实际项目里的目录习惯我更推荐把 XML 放到src/main/resources/mapper/下按模块分目录src/main/resources/mapper/UserMapper.xml src/main/resources/mapper/OrderMapper.xmlyml 保持mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml这样有几点好处不用为 XML 单独改 Maven 资源配置默认就能打包进去。XML 和 Java 分离编译目录干净。mapper-locations用/mapper/**/*.xml不管 Mapper 接口在哪个包只要 XML 的 namespace 写对都能正常匹配。我在实际项目里用的就是这个方案。虽然代码跳转的时候不如同目录方便但配合 IDE 的 MyBatis 插件也能直接跳到 XML影响不大。除非公司规范强制要求同目录否则还是推荐 resources/mapper 方案。5. 一起聊聊 Spring Data JPA 和 MyBatis-Plus 的区别5.1 两者到底在解决什么问题Spring Data JPA 和 MyBatis-Plus 都是 ORM 框架但出发点很不一样。Spring Data JPA 是 JPA 规范的实现核心是“以实体为中心”。你定义好实体类JPA 负责管理表结构映射甚至可以通过ddl-auto自动建表。查询也不需要写 SQL用方法名派生查询或者写 JPQL、Criteria、Specification复杂查询也能用 Specification 拼出动态查询。MyBatis-Plus 是 MyBatis 的增强工具核心是“以 SQL 为中心”。实体和表的映射很简单内置的BaseMapper覆盖单表 CRUD。复杂查询回到 XML 写 SQL每个 SQL 都能精确控制方便 DBA 评审和优化。举个直观的例子查“名字包含某个关键词且年龄大于多少的用户”JPA 可以写方法名ListUser findByNameContainingAndAgeGreaterThan(String name, Integer age);MyBatis-Plus 用 LambdaQueryWrapper 也能几行搞定ListUser list userMapper.selectList(new LambdaQueryWrapperUser() .like(User::getName, name) .gt(User::getAge, age));两者单表场景都很舒服真正的差异在多表关联和复杂查询上。5.2 选型表一张表格说清楚差异维度Spring Data JPAMyBatis-Plus核心思想以实体/对象为中心ORM以 SQL 为中心MyBatis 增强单表 CRUD内置非常省事内置非常省事复杂多表查询需要 JPQL/Criteria/Specification学习成本高XML 写 SQL直观可控SQL 可控性弱自动生成 SQL 可能不容易优化强所有复杂 SQL 都能自己掌控动态查询Specification写法灵活但门槛偏高Wrapper/XML写法直观自动建表支持ddl-auto自动建表/更新不支持自动建表表结构自己管理分页Pageable 内置方法MybatisPlusInterceptor Page运行性能需要理解缓存、lazy loading 等概念SQL 写好以后性能相对透明团队学习成本中高尤其复杂查询低SQL 是后端基本功数据库迁移实体模型耦合较低但查询方言仍需适配SQL 方言敏感迁移时 SQL 要改典型适合场景领域模型复杂、业务对象为核心、快速迭代传统后台管理、报表统计、SQL 优化要求高5.3 我的选型建议我个人的判断标准很简单项目业务围绕明确的领域模型表结构由代码管理团队愿意花时间研究 JPA 规范选 Spring Data JPA。项目里有大量多表关联、统计报表、动态条件查询并且团队里有几个 SQL 写得很溜的人选 MyBatis-Plus。大多数国内互联网企业后台系统尤其报表、管理后台、中台系统MyBatis-Plus 更务实排查问题也更快。Spring Data JPA 绝对不差但它对团队的建模能力和框架理解要求更高。如果你只是想要一个快速 CRUD 框架然后所有复杂查询都要自己写 SQL那 MyBatis-Plus 几乎是最优解。6. 整合过程中最容易踩的坑与排查速查6.1 三个高频报错第一个Invalid value type for attribute factoryBeanObjectType: java.lang.String这个报错我在 Spring Boot 3.3.X 早期 MyBatis-Plus 版本同时出现时遇到过。原因是内置的 MyBatis-Spring 组件和 Spring Framework 6.1 的 Bean 定义解析机制不兼容。解法很简单把mybatis-plus-spring-boot3-starter升级到 3.5.7 或更新版本问题就消失了。第二个Invalid bound statement (not found)这个前面提到过几乎都是 XML 没有被打入 classpath或者mapper-locations配置路径和实际路径对不上。排查顺序是先看target/classes目录下有没有对应的 XML没有就是 Maven 资源问题有就是 yml 路径写错或者 namespace 没核对。第三个项目启动成功但所有查询没有分页效果这个基本可以断定没有注册MybatisPlusInterceptor。Spring Boot 启动日志里会有一条分页插件相关的初始化信息如果看不到就回去检查配置类有没有被扫描到或者是不是在配置类里重复 new 了MybatisPlusInterceptor。6.2 排查思路遇到 MyBatis-Plus 相关的问题我一般按以下顺序排查看启动日志有没有异常重点是Caused by部分。看target/classes下 XML 是否真实存在。看 yml 配置是否生效可以临时加log-impl: org.apache.ibatis.logging.stdout.StdOutImpl打印 SQL。看MapperScan扫描包路径和 Mapper 接口位置是否一致或者 MapperMapper注解是否遗漏。看依赖树mvn dependency:tree确认没有多个 MyBatis-Plus 版本冲突。这套流程基本能解决 90% 的整合问题。6.3 规避清单根据自己的维护经验我整理了一条规避清单永远使用mybatis-plus-spring-boot3-starter不要用旧版 starter。主键策略统一配置不要每张表手动写。分页插件和逻辑删除是全局配置集中管理。XML 写在resources/mapper下减少资源拷贝问题。所有 Long 主键字段统一转 String 输出避免前端精度丢失。table字段命名规范统一避免映射混乱。升级依赖前先看官方 release notes尤其是大版本升级。这些细节比我实际写代码的时间还影响项目稳定性。7. 实战中养成的习惯最后再分享几个我个人在实际项目里长期养成的习惯。每次新项目整合完 MyBatis-Plus我都会第一时间写一个“冒烟测试”接口插入、查询、分页、逻辑删除、自动填充全走一遍。不要等到业务功能写了一半再回来查框架问题那样排查成本太高。这个冒烟测试可以直接用SpringBootTest写成单元测试后续每次升级依赖还能回归跑一遍。还有就是对 XML 的校验。很多人写 SQL 很随意连表名、字段名都不反引号等到了生产环境才发现某个字段是 MySQL 关键字。我在项目里会加一个规范所有 XML 里的 SQL 必须先在数据库客户端里执行一次再粘到项目里。这个习惯帮我挡掉了不少低级错误。Spring Boot 3.3.X 和 MyBatis-Plus 整合本身不难难点全在细节。版本选对、配置看全、XML 路径放好、常用拦截器注册上剩下的就是按平时的 MyBatis 经验写业务。希望这篇内容能让你少走几次我走弯路的过程。
返回列表