news 2026/9/18 16:34:18

Spring Boot 3整合MyBatis-Plus连接MySQL完整指南与避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 3整合MyBatis-Plus连接MySQL完整指南与避坑实践

1. 为什么 Spring Boot 3 让很多人卡在了第一步

说个真实经历,我接手过一个老项目升级的活儿,pom 文件里 Spring Boot 版本从 2.7 升到 3.2,结果编译报错差点把人整崩溃。不是业务代码的问题,而是依赖之间的版本兼容关系全变了。Spring Boot 3 的这次升级,本质上是一次“底层地基更换”:从 Java EE 规范迁移到了 Jakarta EE 9+,包名javax.*全部改成了jakarta.*,Java 版本基线直接抬到了 17。如果你用的是 Spring Boot 3,但 JDK 还停留在 8,那连运行都起不来。

这也就直接导致了一个非常现实的问题:很多网上搜到的教程,尤其是 2022 年之前写的,拿过来大概率是跑不通的。光是spring-boot-starter-web里那个javax.servlet改成jakarta.servlet,就足够让一堆复制粘贴的同学浪费一整天。而 MySQL 连接、MyBatis-Plus 整合这块,踩的坑更多——驱动类名、数据库方言、分页插件、逻辑删除、自动填充这些,每一个点在新版本里都有讲究。

先说结论:Spring Boot 3 下连接 MySQL 并整合 MyBatis-Plus,核心就三件事——依赖引对、配置写对、 插件配好。听起来简单,但每一件都有细节。这篇文章不整虚的,给的是我在实际项目里反复验证过的完整流程,以及踩坑之后的良药。

1.1 版本基线:JDK 17、Spring Boot 3.x、MyBatis-Plus 3.5.3+

这可能是整个项目最容易被忽略的一环。很多人一上来就去写配置,结果项目都启动不了,回头才发现基础环境压根没达标。

我现在的标准配置是:

组件版本要求说明
JDK17+Spring Boot 3 强制要求,推荐用 17 LTS
Spring Boot3.2.x / 3.3.x3.2 之后对 MyBatis-Plus 兼容更稳定
MyBatis-Plus3.5.5+(建议 3.5.7)3.5.3 开始才支持 Spring Boot 3
MySQL5.7 / 8.0+8.0 建议用mysql-connector-j驱动
Maven3.6.3+低版本对 JDK 17 支持不友好

这里有个很容易踩的雷:如果你用mybatis-plus-boot-starter,一定要确认版本号。3.5.3 之前的老版本根本没有针对 Spring Boot 3 做过适配,启动时会报ClassNotFoundException: javax.sql.DataSource之类的异常。尤其是从网上抄的依赖,版本经常是老的 3.4.x、3.5.1,那基本是必炸。

顺带提一句,网上有一批教程还在教用com.mysql:mysql-connector-java这个坐标。Spring Boot 3 之后,官方把驱动坐标统一改成了com.mysql:mysql-connector-j。虽然老的坐标在大多数情况下也能拉下来,但依赖传递时容易和 Spring Boot 自带的版本管理打架,建议直接用新的。

2. 初始化 Spring Boot 3 工程的两种姿势

如果你想从零开始搭建,有两条路可以走,我都试过,各有优劣。

2.1 通过 Spring Initializr 创建(推荐)

打开 start.spring.io ,项目类型选 Maven,语言 Java,Spring Boot 选 3.2.x 或更高版本,依赖里搜索并添加Spring WebMySQL Driver就足够起步了。

有一点必须提醒:Initializr 生成的工程默认是 JDK 17 编译级别。如果本机装的 Maven 是 3.5 以下的,构建时会直接报错。建议直接用 Maven 3.8+,或者干脆用 IDEA 自带的 Maven 也行。

2.2 手动创建 Maven 工程(理解结构更好的方式)

如果你喜欢把每一个依赖都在自己掌控下,手动创建也行。核心 pom 依赖如下:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MySQL 8.x 驱动,Spring Boot 3 依赖管理会帮我们锁定版本 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- MyBatis-Plus Spring Boot3 专用 starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

注意看mybatis-plus-spring-boot3-starter这个坐标,它和老的mybatis-plus-boot-starter不是同一个。Spring Boot 3 场景必须用带spring-boot3的版本,这个是 MyBatis-Plus 官方在 3.5.3 时做的拆分,很多老教程没提到这点,抄错就白折腾了。

3. MySQL 数据源配置:看似简单,坑全在细节里

依赖解决了,接下来就是application.yml里的数据源配置。这里建议用 YAML 格式,代码整洁,而且能写注释,方便维护。

3.1 基础配置

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456

这一段有四个细节值得注意:

  • driver-class-name 到底写什么?MySQL 8+ 驱动类名是com.mysql.cj.jdbc.Driver,不是老教程里的com.mysql.jdbc.Driver。后者在 MySQL 8 驱动里已经被标记为过时,真跑起来可能会有兼容问题。
  • serverTimezone=Asia/Shanghai,这是必须的。如果不指定时区,MySQL 8 驱动默认会用服务器时区,往往和本机差 8 小时,查出来的时间全是错的。这个坑我印象太深了,之前有个项目查create_time总是慢 8 小时,排查了半天最后发现就是没配时区。
  • allowPublicKeyRetrieval=true,这个比较特殊。MySQL 8 的 caching_sha2_password 加密插件默认要求 SSL 或 RSA 公钥传输,如果不加这个参数,首次连接时会报Public Key Retrieval is not allowed。本地开发环境直接加上,线上环境再看情况处理。
  • useSSL=false,本地开发不要启用 SSL,否则每次连接都多一次握手,慢不说,偶尔还会报证书相关的警告。

3.2 日志配置与 SQL 输出

整合了 MyBatis-Plus 之后,第一步肯定是想看看 SQL 到底是怎么执行的。在application.yml里加一段:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这样会把你项目里所有的 SQL 语句和参数打印到控制台。格式长这样:

==> Preparing: SELECT id,name,age,email,deleted FROM user WHERE deleted=0 AND id=? ==> Parameters: 1(Long) <== Total: 1

生产环境记得把这行删掉,或者改成日志级别控制,不然 SQL 全量输出到日志文件里,不仅占空间,还可能有敏感数据泄露风险。

3.3 连接池参数(基于 HikariCP)

Spring Boot 3 默认的数据库连接池是 HikariCP,基本不需要额外配置也能用,但高并发场景下建议做一些显式调优:

spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000

这些参数通常不需要动,但是如果你在压测时发现连接获取超时,第一反应应该是来看这里的值。最大连接数不是越大越好,20 在绝大多数业务场景下已经够用了。

4. MyBatis-Plus 3.5.x 整合:从写实体类到跑通 CRUD

依赖和数据源都齐了,接下来就是 MyBatis-Plus 的正式整合。这个环节是重头戏,很多问题都出在这里。

4.1 核心配置与大写映射关系

mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

map-underscore-to-camel-case: true意味着数据库字段user_name能自动映射到 Java 属性userName,这是 MyBatis-Plus 最省心的地方。如果不想用自动映射,注解@TableField可以手动指定列名,但能省则省。

id-type: auto表示主键使用数据库自增策略,这样新增操作时不用手动设置 id。

关于logic-delete-field,这是 MyBatis-Plus 的逻辑删除配置,全局指定之后,所有带deleted字段的表都会自动处理逻辑删除,查询时自动追加deleted=0条件,删除会自动变成UPDATE ... SET deleted=1。这一点很重要,等会儿专门说。

4.2 实体类与数据表的映射关系

以一张最简单的用户表为例:

CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30), age INT, email VARCHAR(50), deleted TINYINT(1) DEFAULT 0, create_time DATETIME, update_time DATETIME );

对应的实体类:

@Data @TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; @TableLogic private Integer deleted; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }

几个点解释一下:

  • @TableName:如果类名和表名一致,可以省略。不一致时必须指定,大小写不敏感。
  • @TableLogic:逻辑删除注解,配合全局配置使用。也可以用@TableLogic(value = "0", delval = "1")显式指定逻辑未删除值和已删除值,防止全局配置漏配。
  • @TableField(fill = FieldFill.INSERT):这是自动填充的关键注解,配合MetaObjectHandler一起用,后面细说。
  • 注意create_timeupdate_time这些字段类型,用了 LocalDateTime,千万别再用java.util.Date,在 JDK 17 环境下java.util.Date处理 LocalDateTime 的序列化问题会很麻烦。

4.3 Mapper 层与扫描配置

Mapper 接口继承BaseMapper<T>,顺手就把单表 CRUD 全拿下了:

@Mapper public interface UserMapper extends BaseMapper<User> { }

@Mapper注解可以写在每个 Mapper 接口上,也可以写在启动类上通过@MapperScan(" com.example.demo.mapper")统一扫描。我建议用@MapperScan,一个注解全搞定,不用在接口上反复标记。

MyBatis-Plus 3.5.x 里BaseMapper自带的方法已经覆盖了绝大多数单表操作:selectByIdselectListselectOneinsertdeleteByIdupdateByIdselectPage等。真正需要手写 SQL 的业务场景其实越来越少。

4.4 启动类与测试

启动类不用特殊处理,确保在根包下就行:

@SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

写个测试类验证一下整套链路通不通:

@SpringBootTest class UserMapperTest { @Autowired private UserMapper userMapper; @Test void contextLoads() { User user = new User(); user.setName("张三"); user.setAge(25); user.setEmail("zhangsan@example.com"); int rows = userMapper.insert(user); System.out.println("插入条数: " + rows); System.out.println("回填主键: " + user.getId()); } }

注意一个细节:MyBatis-Plus 的insert成功之后,会把数据库自增的主键回填到传入对象的id属性上。这是很实用的功能,后续如果要用到这个 id 去操作关联数据,就不用再查一遍了。

5. 分页插件:MyBatis-Plus 最实用也是最容易配错的功能

分页应该是日常开发里用到最多的功能之一。MyBatis-Plus 的分页插件,配好了就是神器,配不好就是各种分页失效、内存分页的假象。

5.1 配置方式(注意版本差异)

老版本的配置方式是通过@Bean向容器里注入PaginationInnerInterceptor,配置类上还有一个@MapperScan注解。Spring Boot 3 下面依然是这个套路,但如果你用的是 3.5.9 之后的版本,MybatisPlusInterceptor的包名和 API 基本没变,放心用:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); // 设置单页最大条数,防止有人恶意传一个超大页码 paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }

DbType.MYSQL一定要指定,别偷懒省略。PaginationInnerInterceptor会根据数据库类型生成不同的分页 SQL,如果数据库类型传错或为空,某些操作会走通用的内存分页,数据量一大就卡死。

5.2 分页查询的实际用法

Page<User> page = new Page<>(1, 10); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getAge, 20) .orderByAsc(User::getId); Page<User> userPage = userMapper.selectPage(page, wrapper); System.out.println("总记录数: " + userPage.getTotal()); System.out.println("当前页数据: " + userPage.getRecords());

这里想强调一个细节:selectPage返回的Page对象里,total默认是自动查询的。Page构造第一个参数是页数(从 1 开始),第二个参数是每页条数。有些新手会把页数传 0,那查出来的结果就会不对。MyBatis-Plus 的Page如果页码从 0 开始,有些版本会直接把第一页数据当成第二页返回,这个要特别留意。

5.3 手写 SQL 时如何让分页生效

项目里难免有一些复杂的多表联查,需要手写 SQL。如果想让自定义 SQL 也支持 MyBatis-Plus 分页,Mapper 接口里写成这样:

@Mapper public interface UserMapper extends BaseMapper<User> { Page<User> selectUserPage(Page<User> page, @Param("age") Integer age); }

XML 里正常写查询语句,不需要带LIMIT

<select id="selectUserPage" resultType="com.example.demo.entity.User"> SELECT * FROM user <where> <if test="age != null"> AND age = #{age} </if> </where> </select>

MyBatis-Plus 的分页插件会自动在 SQL 后面拼接LIMIT,你只需要保证 Mapper 方法的第一个参数是Page对象即可。这个约定要是忘了,分页插件就不会拦截执行,方法会被当成普通查询处理。

6. 逻辑删除、自动填充、乐观锁:日常开发高频能力

这三个能力是 MyBatis-Plus 的杀手锏级特性,配置对了能让代码量断崖式减少。

6.1 逻辑删除的完整链路

逻辑删除的核心价值是“不删数据、只标记状态”,在审计类系统里几乎是标配。

全局配置:

mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

实体类上对应的字段加@TableLogic,之后调用userMapper.deleteById(1L)时,真实执行的 SQL 是:

UPDATE user SET deleted=1 WHERE deleted=0 AND id=?

执行selectById(1L)时,自动追加:

SELECT id,name,age,email,deleted FROM user WHERE deleted=0 AND id=?

这个设计的好处显而易见,误删也能救。但坏处也很隐蔽:

  • 数据库唯一索引要小心。比如用户表手机号设置了唯一索引,用户点删除之后deleted变成了 1,此时再注册同手机号的新用户,会触发唯一索引冲突。我们当时处理方案有两种:一种是把唯一索引改成(phone, deleted)联合索引;另一种是删除时给 phone 拼一个时间戳后缀,类似13800000000_deleted_1699999999
  • 逻辑删除字段索引问题。如果没有给deleted建立合适的索引,扫表时每次都会带上deleted=0这个条件,大表上性能会受影响。

6.2 自动填充:创建时间、更新时间不再手动 set

每次 insert/update 都要手动给createTimeupdateTime赋值,写多了真的会烦。MyBatis-Plus 的自动填充机制就是用来干这个的。

先定义字段注解(上面实体类里已经提到),然后实现一个MetaObjectHandler

@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()); } }

这样以后凡是实体类里标了insertFillupdateFill的字段,在 MyBatis-Plus 执行 SQL 之前会自动填充值,业务代码里再也不用写那两行set了。

有一点容易踩坑:如果实体类字段用了private LocalDateTime createTime但在实体里自己赋了初值,自动填充可能会不生效strictInsertFill的语义是只在字段为空时才去填充,如果你在 setter 里已经给了值,它就不覆盖了。所以自动填充的字段最好保持 null,让框架来处理。

6.3 乐观锁:避免超卖和覆盖更新

多用户同时修改同一行数据时,后提交的人很容易把先提交的人覆盖掉。MyBatis-Plus 提供了乐观锁插件,基于版本号机制。

配置里加一个拦载器,注意顺序:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; }

实体类加版本字段:

@Version private Integer version;

数据库表加version字段,默认值 0。这样执行updateById时,框架自动生成:

UPDATE user SET name=?, version=1 WHERE id=? AND version=0

如果更新到的行数为 0,说明版本已经被别人改了,你需要重试或提示用户。这个机制在处理并发更新场景时特别有用,比如库存扣减、余额变更。

7. 条件构造器:LambdaQueryWrapper 的正确打开方式

如果你还在拿QueryWrapper拼字符串列名,你在 MyBatis-Plus 3.5.x 下的体验会很痛苦。推荐直接用LambdaQueryWrapper,编译期就能检验字段名是否写错。

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getAge, 20) .like(User::getName, "张") .between(User::getId, 1L, 100L) .orderByDesc(User::getId); List<User> users = userMapper.selectList(wrapper);

User这个 lambda 引用的形式可以看出,lambda 写作User::getName,写错字段名时编译期报错,能够早一点发现问题。但条件构造器也有自己的限制。

比较常见的坑是:条件构造器里用了 OR 时没有加括号,SQL 拼接逻辑直接错了。

比如想表达(age = 20 OR email LIKE '%@qq.com') AND deleted = 0

wrapper.eq(User::getAge, 20) .or() .like(User::getEmail, "@qq.com") .eq(User::getDeleted, 0);

这段代码最终生成的 where 条件会变成age = 20 OR (email LIKE '%@qq.com' AND deleted = 0),和你想要的完全不一样。

正确写法:

wrapper.and(w -> w.eq(User::getAge, 20) .or() .like(User::getEmail, "@qq.com")) .eq(User::getDeleted, 0);

这个陷阱在我刚用 MyBatis-Plus 的时候就踩过,导致查出了不少已删除的数据,最后定位到 SQL 才发现括号问题。

8. 事务:@Transactional 在 Spring Boot 3 里的正确姿势

整合 MyBatis-Plus 之后,事务依然是基于 Spring 的@Transactional注解,这个没变。

@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) public void createWithTransaction(User user) { userMapper.insert(user); // 模拟后续操作,抛异常则全部回滚 if (user.getAge() < 0) { throw new RuntimeException("年龄非法"); } } }

两个点必须记住:

  • rollbackFor = Exception.class这句一定要写。默认情况下 Spring 只对RuntimeExceptionError回滚,如果你抛的是自定义的 checked exception,不加这个参数事务是不会回滚的。这个细节够坑,我见过不止一次生产事故就是这个原因。
  • @Transactional加在private方法上无效。Spring 的事务是基于 AOP 动态代理实现的,private方法根本不会被代理拦截。如果同一个类里有两个方法A()调用B()B加了事务注解,也不会生效,因为走的是this的引用而非代理对象。

9. 手写复杂 SQL:XML 还是注解?

MyBatis-Plus 把单表 CRUD 都包了,但复杂查询你还是需要自己写 SQL。这里的选择会影响你整个项目的可维护性,我比较推荐 XML 方案。

9.1 XML 方式

resources/mapper/目录下建一个UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <select id="selectUserWithOrders" resultType="com.example.demo.vo.UserVO"> SELECT u.*, o.order_no FROM user u LEFT JOIN `order` o ON o.user_id = u.id WHERE u.deleted = 0 <if test="name != null and name != ''"> AND u.name LIKE CONCAT('%', #{name}, '%') </if> ORDER BY u.id DESC </select> </mapper>

别忘了在application.yml里配置 mapper XML 的路径:

mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml

classpath*:(带星号)和classpath:(不带星号)区别很大。带星号可以扫描到多个 jar 包里的 XML 文件,如果你的项目是多模块结构,建议直接用带星号的形式。

9.2 注解方式

简单 SQL 用注解确实舒服:

@Select("SELECT * FROM user WHERE age > #{age}") List<User> selectByAge(Integer age);

但 SQL 一长,注解里写起来就很难看,而且 XML 可以动态 SQL 的方式处理更复杂的标签组装,涉及的 if、where、foreach 在 XML 里也更直观。我的经验是:单表操作交给 MyBatis-Plus 内置方法,多表查询和复杂统计统一走 XML。

10. 从 0 到 1 跑通一个接口的完整案例

前面把各种配置和能力都过了一遍,现在完整地串一下:从数据库表到 GET 接口,一个项目是怎么跑起来的。

10.1 表结构

CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, age INT DEFAULT 0, email VARCHAR(50), deleted TINYINT(1) DEFAULT 0, version INT DEFAULT 0, create_time DATETIME, update_time DATETIME ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

version字段是给乐观锁用的,deleted给逻辑删除用。

10.2 Controller + Service + Mapper

Controller 层:

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public Result<User> getUserById(@PathVariable Long id) { return Result.success(userService.getById(id)); } @GetMapping public Result<Page<User>> pageUsers(@RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size) { return Result.success(userService.pageUsers(current, size)); } }

Service 层:

@Service public class UserService { @Autowired private UserMapper userMapper; public User getById(Long id) { return userMapper.selectById(id); } public Page<User> pageUsers(Integer current, Integer size) { Page<User> page = new Page<>(current, size); return userMapper.selectPage(page, new LambdaQueryWrapper<User>().orderByDesc(User::getId)); } }

这样一个用户查询接口就通了。请求GET /api/user/1会打到 MySQL,执行一条SELECT,然后把结果以 JSON 格式返回,MyBatis-Plus 自动帮你加了逻辑删除过滤、字段映射等逻辑。

11. 踩坑实录:那些被版本和配置折磨的夜晚

最后这部分,把我在实际项目中踩过的、以及在帮别人排查时见到的高频问题做一个汇总,能帮你省下大量时间。

11.1 Invalid bound statement (not found)

这是最常见的报错,没有之一。原因基本都是 Mapper XML 文件和 Mapper 接口没有建立映射。

排查链路:

  1. 检查 XML 的namespace是否和接口全限定名一致。
  2. 检查 XML 里的id是否对应方法名。
  3. 检查mapper-locations是否配置正确,文件是否打进了target/classes
  4. 检查 Mapper 接口是否被@Mapper@MapperScan扫描到。

11.2 数据库连接失败:Public Key Retrieval is not allowed

连接 MySQL 8 时出现这个错误:

Caused by: java.sql.SQLException: Public Key Retrieval is not allowed

原因:MySQL 8 默认使用caching_sha2_password认证插件,而连接是加密的,驱动首次连接需要获取服务器公钥。解决方案在 JDBC URL 上加参数:allowPublicKeyRetrieval=true&useSSL=false

11.3 mybatis-plus 自动填充不生效

检查三点:

  • 实体字段有没有加@TableField(fill = FieldFill.X)
  • MetaObjectHandler有没有被 Spring 管理(是不是加了@Component)。
  • 执行 insert 时,是不是通过 MyBatis-Plus 的insert/updateById执行的。如果你用自定义 SQL 方式直接 insert,自动填充不会干扰你的 SQL,注意确保 SQL 里包含了这些字段。

11.4 分页插件不生效,查出来全部数据

可能的原因:

  1. 分页拦载器没配置或没被加载。
  2. Mapper 方法第一个参数不是Page类型。
  3. 你调用的是selectList而不是selectPage
  4. 自定义 SQL 语句没有把Page放在第一位参数。

11.5 Spring Boot 3 下 javax 包名冲突

如果你的项目里出现类似javax.servlet找不到类的错误,基本都是因为引了某些老依赖传递了javax包。在 Spring Boot 3 中应该全部使用jakarta.*包。排查时检查mvn dependency:tree,把老依赖排除掉即可。

12. 项目结构总览与后续扩展方向

一套标准化的 Spring Boot 3 + MySQL + MyBatis-Plus 项目结构长这样:

com.example.demo ├── DemoApplication.java # 启动类,@SpringBootApplication + @MapperScan ├── config │ ├── MybatisPlusConfig.java # 分页插件、乐观锁插件 │ └── MyMetaObjectHandler.java # 自动填充处理器 ├── controller │ └── UserController.java ├── service │ └── UserService.java ├── mapper │ └── UserMapper.java ├── entity │ └── User.java └── vo / dto └── UserVO.java

resources/mapper/下放 XML 文件,application.yml统一配置数据源、MyBatis-Plus、日志级别等。

整个体系跑通之后,后面可以研究的内容还挺多的:

  • p6spy格式化控制台 SQL,看参数比 StdOutImpl 更舒服。
  • MyBatis-Plus 的Db静态工具类,不用注入 Mapper 就能做简单的 CRUD。
  • 多数据源切换,用dynamic-datasource-spring-boot3-starter
  • 代码生成器,用mybatis-plus-generator一键生成实体、Mapper、Service、Controller。
  • 分布式场景下的并发控制,可以试试@Version乐观锁与 Redisson 分布式锁的配合。

我个人在实际操作中的体会是:MyBatis-Plus 把单表 CRUD 的复杂度几乎打到了零,但凡是省事儿的功能,背后都有一套自己的约定。配置对了,它是效率引擎;配置错了,它比手写 MyBatis 还让人头大。所以每次搭新项目,我第一件事就是把mybatis-plus的版本钉死,然后从分页插件和自动填充开始配,这两块最容易忘、也最容易出问题。希望这篇内容能帮你跳过那些让人抓狂的夜,直接把项目跑起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 16:33:12

正弦稳态电路向量法:从复数阻抗到功率计算全解析

简介&#xff1a;《用向量法表示电路》PPT学习教案面向电路分析初学者与教学者&#xff0c;系统讲解如何以复数与相量法表示和求解正弦交流电路&#xff0c;帮助读者突破从时域到相量域的抽象难点。压缩包内仅有1个pptx文件&#xff0c;大小482KB&#xff0c;以图文教案形式呈现…

作者头像 李华
网站建设 2026/9/18 16:33:10

基于51单片机的电子密码锁设计:矩阵键盘、状态机与AT24C02掉电存储

简介&#xff1a;一份基于51单片机的电子密码锁设计完整报告&#xff0c;适用于单片机课程设计、毕业设计及实训报告撰写&#xff0c;适合初、中级学习者参考。压缩包仅含1个docx文档&#xff0c;整体约1.52MB&#xff0c;轻量便于下载阅读&#xff0c;目前已有177人浏览学习。…

作者头像 李华
网站建设 2026/9/18 16:31:45

基于STM32的图书馆环境监测系统:代码、原理图与仿真全开源

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 16:31:21

MySQL 存量表补主键:InnoDB 聚簇索引、数据清洗与在线 DDL 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 16:31:20

AWS实战指南:从核心服务选型到CLI部署与事件驱动架构

简介&#xff1a;这是一份《云计算》第三版配套课件《Amazon云计算AWS介绍》PPT&#xff0c;面向高校师生、云计算初学者及方案架构师&#xff0c;系统梳理AWS核心服务与典型应用场景。资源共1个pptx&#xff0c;容量2.85MB&#xff0c;内容覆盖基础存储架构Dynamo、弹性计算云…

作者头像 李华