不想从零开始敲一个空项目,也不想被一堆 XML 配置文件劝退,那 SpringBoot3 搭配 MyBatis-Plus 基本就是当前 Java 后端搭建工程最顺手的组合之一。这篇文章我会直接带你走一遍完整流程:从环境确认、依赖引入、配置编写,到实体类、Mapper、Service、Controller 落地,最后再验证接口能不能通。整个流程走完,你本地应该已经跑起来一个可以 CRUD 的 SpringBoot3 工程。适合刚接触 Spring Boot 3,或者从 Spring Boot 2 迁移上来、想快速适配 MyBatis-Plus 新同步版本的开发者。
1. 项目整体思路与组合选择
1.1 SpringBoot3 改了什么,为什么直接选 3.x
Spring Boot 3.x 最大的变化是底层换成了 Spring Framework 6 和 Jakarta EE 9+,原来那一堆 javax.* 包名全部迁到了 jakarta.*。别小看这个包名迁移,它直接影响你引入第三方依赖时的版本选择,很多老库不升级就是启动报 ClassNotFoundException。工程构建的基线也提到了 JDK 17,不再是 Spring Boot 2.x 时代 JDK 8 就能跑。
选 SpringBoot3 的原因很直接:新项目没必要抱着旧版本不放。Spring Boot 2.x 虽然还在维护期,但新特性和新组件都优先落在 3.x 上。从实际经验来看,Spring Boot 3.2、3.3 这些版本对 JDK 17 和 21 的支持已经很成熟,日常 Web 服务、接口开发、数据库访问这些场景完全够用。如果你是从老项目迁移上来,心里要有个数:不仅仅是升级 Spring Boot 版本号那么简单,而是需要把整个依赖树里涉及 javax 的库都过一遍,找到对应的 jakarta 兼容版本。
1.2 MyBatis-Plus 解决什么痛点
MyBatis-Plus 是我个人在中小型项目里特别喜欢用的持久层框架,它没有把你锁死在复杂的 XML 里,也没有完全替代 MyBatis 的能力。它的核心价值就是“增强”:单表 CRUD 几乎不用手写 SQL,Mapper 接口继承一个基类就自带增删改查、分页、逻辑删除这些能力。复杂查询该写 SQL 的时候,照样能用注解或 XML 写自定义语句,灵活度没有丢掉。
在 SpringBoot3 下用 MyBatis-Plus 需要注意一个关键点:从 3.5.3 版本开始,官方提供了专门适配 Spring Boot 3 的同步版本,坐标不再是原来的 mybatis-plus-boot-starter,而是 mybatis-plus-spring-boot3-starter。很多人第一次搭工程就卡在这里,照着网上老教程引入旧坐标,结果启动直接报错。后面第 3 节我会专门把依赖写法放大讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建前的环境确认
2.1 JDK 与 IDE 准备
先说结论:JDK 必须 17 以上。Spring Boot 3 官方在编译层面就直接限制了 JDK 17,你本地哪怕是 JDK 8 装了一堆环境变量,跑 3.x 项目也会在构建阶段直接失败。我自己习惯直接用 JDK 21,长周期支持版本,配合 Spring Boot 3.x 跑得很稳。如果你机器上装了多个 JDK,建议在工程里显式指定,别依赖全局环境变量。
IDE 方面,IntelliJ IDEA 社区版其实就够了,因为它本身就免费,而且对 Spring Boot 工程的支持很好。如果要用一些付费插件功能,那是后话,至少我们“5 分钟快速搭建”这个场景社区版完全没问题。没有 IDE 的话,纯命令行也能搞:先用 curl 请求 Spring Initializr 拿到压缩包,再用 Maven 打包运行。但强烈不建议新手用命令行打,因为配置文件和启动日志可视化排查都不方便,IDE 里有日志面板和断点调试,省的时间远超过创建工程那几秒。
检查 JDK 版本直接跑一条命令:
bash复制java -version
看到 17 或者更高版本号就可以,如果是 1.8 开头的版本,先去装新 JDK 再回来。
2.2 数据库实例准备
既然要用 MyBatis-Plus,那一个能连的数据库实例是少不了的。MySQL、PostgreSQL 都可以,我这里以 MySQL 8.x 为例。先确认本地装上 MySQL,或者能用 Docker 起一个临时实例:
bash复制docker run --name demo-mysql -e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=demo_db -p 3306:3306 -d mysql:8.0
如果没有 Docker,直接本机安装 MySQL 也行。重点是确保端口、用户名、密码都是你自己可控的,后面配置里写的就是这串连接信息。建库这一步很重要,因为 Spring Boot 启动时不会自动帮你建数据库,数据库不存在直接连接报错。
code复制
CREATE DATABASE demo_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
申请数据库之后,最好先手动建一张测试表,否则项目起来后 Mapper 接口扫描不到表,查询接口跑起来也是报错。表结构在后面第 4 节会给完整 SQL,这里先把库准备好就行。
3. 用初始化器生成工程骨架
3.1 修改 Maven 依赖,关键变化在这里
我用 Spring Initializr 生成基础骨架,访问网站之后填三样核心信息:构建工具选 Maven,语言选 Java,Spring Boot 版本选稳定版 3.x。 Group 和 Artifact 按你自己项目习惯填就行,比如 com.example 和 quick-start。依赖这里可以先选一个 Spring Web,其他都可以在后面手动加。
生成之后打开 pom.xml,你会发现 Spring Boot 3 默认带的是 spring-boot-starter-web,这没问题。现在要手动加入 MyBatis-Plus 的同步版本坐标:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
这里我强调一下为什么不是 mybatis-plus-boot-starter。在 Spring Boot 3 里,原来的 starter 因为内部依赖的还是 javax.servlet 和 Spring Boot 2.x 的自动配置机制,直接拿来用会出现自动配置类加载不到的问题。官方后来专门拆分出带 spring-boot3 标识的 artifact,内部把 javax 换成了 jakarta,自动配置类也重新适配过。
MySQL 驱动这个坐标也注意一下:Spring Boot 3 管理下,mysql:mysql-connector-java 这个老坐标已经不用了,新坐标是 com.mysql:mysql-connector-j,而且不需要写版本号,由 Spring Boot 的 dependencyManagement 统一管。
如果你习惯用 Lombok 简化实体类,那再加一个 lombok 依赖。这个过程没必要记版本号,让你选的工程初始化器会处理好,或者你直接复制我上面这几段到 pom.xml。
3.2 application.yml 配置,连接信息往这里填
Spring Boot 3 工程默认生成的是 application.properties,我一般会直接改成 application.yml,YAML 格式层级关系清楚,配置文件长一点也不会显得乱。内容如下:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: assign_id
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
逐行解释几个关键参数:
serverTimezone=Asia/Shanghai:如果不设置时区,MySQL 连接经常会报 CST 时区相关的警告或错误。尤其是在国内服务器上,这个参数基本是标配。map-underscore-to-camel-case: true:数据库字段如果叫user_name,Java 属性写userName,开启这个配置后自动完成映射,不用每个字段都加@TableField注解。log-impl: StdOutImpl:让 MyBatis 把执行 SQL 直接打印到控制台,开发阶段排查问题非常有用。上线前记得去掉或者换成日志框架的适配实现。id-type: assign_id:主键策略用雪花算法生成,不依赖数据库自增。这在分库分表场景里更灵活,也避免每次插入都要关心主键字段。
配置文件改完后,你的工程骨架已经具备连接数据库和加载 MyBatis-Plus 的基础能力了,真正跑起来还需要几行核心代码。
4. 核心业务代码编写
4.1 实体类与表结构
先准备表结构。我建一张简单但完整的 user 表,带自增主键、用户名、邮箱、创建时间和逻辑删除标记:
sql复制CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`email` VARCHAR(100) DEFAULT NULL,
`age` INT DEFAULT NULL,
`deleted` TINYINT(1) NOT NULL DEFAULT 0,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
对应的实体类,我建议直接用 Lombok,写起来干净很多:
java复制package com.example.quickstart.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String email;
private Integer age;
@TableLogic
private Integer deleted;
private LocalDateTime createTime;
}
几个注意点:
@TableName("user"):如果表名和实体类名不一致,用这个注解指定。如果一致可以省略,但写出来更保险。@TableId(type = IdType.AUTO):主键字段,AUTO表示数据库自增。如果你在application.yml里配了全局assign_id,这里建议单独指定成AUTO,否则会覆盖全局配置。@TableLogic:逻辑删除字段。这个注解很实用,调用 MyBatis-Plus 的deleteById不会真的删除数据,而是把deleted更新为 1。查询的时候,MP 也会自动加上WHERE deleted=0条件。
实体类里尽量用包装类型而不是基本类型,比如 Integer age 而不是 int age,这样数据库里 NULL 值才能正确映射到 Java 的 null,避免各种空指针问题。
4.2 Mapper 接口与枚举
有了实体类,接下来是最简单也最核心的 Mapper 层。MyBatis-Plus 的 Mapper 接口只需要继承 BaseMapper<T>,不需要写任何实现类,也不需要 XML 文件:
java复制package com.example.quickstart.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.quickstart.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
这里可能有人会问:一个空接口能干什么?答案都在 BaseMapper 里。它预置了十几个单表操作方法,比如 selectById、selectList、insert、updateById、deleteById,这些方法名和 SQL 语句的对应关系是 MyBatis-Plus 内部注册好的,你只需要继承就能直接调用。
如果需要更多查询能力,比如条件构造器 QueryWrapper,那是在 Service 或 Controller 层使用,Mapper 接口本身保持干净。比如动态拼接 SQL、自定义多表 join,那就在接口里加方法并用 @Select 注解或者 XML 实现,这一点上 MyBatis-Plus 和原生 MyBatis 能力一致,不会成为限制。
4.3 Service 层封装
Service 层建议继承 MyBatis-Plus 提供的 ServiceImpl。这样做的好处是直接获得一套健壮的通用业务逻辑方法,比如 saveOrUpdate、page、list,这些在业务上高频使用:
java复制package com.example.quickstart.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.quickstart.entity.User;
public interface UserService extends IService<User> {
}
实现类写起来更短:
java复制package com.example.quickstart.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.quickstart.entity.User;
import com.example.quickstart.mapper.UserMapper;
import com.example.quickstart.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
很多新手上来会疑惑 Service 层存在的意义。最直接的解释:Controller 不直接调用 Mapper,而是在 Service 层里写业务逻辑。比如“新增用户前检查邮箱是否重复”“删除用户时记录审计日志”,这些逻辑放到 Service 层里,Controller 只负责接收参数和返回结果,职责分明。工程规模一上来,这种分层的价值会立刻体现出来。
4.4 Controller 与统一返回
最后写一个 Controller 做一个简单的 CRUD 接口,让大家看完能直接复制到工程里测试。设计接口时我建议包一个统一的返回结构,而不是直接返回实体类或 null。这里定义一个简单的结果封装类:
java复制package com.example.quickstart.common;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> r = new Result<>();
r.code = 0;
r.message = "success";
r.data = data;
return r;
}
public static <T> Result<T> fail(String message) {
Result<T> r = new Result<>();
r.code = 500;
r.message = message;
return r;
}
}
Controller:
java复制package com.example.quickstart.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.quickstart.common.Result;
import com.example.quickstart.entity.User;
import com.example.quickstart.service.UserService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public Result<User> create(@RequestBody User user) {
userService.save(user);
return Result.ok(user);
}
@GetMapping
public Result<List<User>> list(@RequestParam(required = false) String name) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
if (name != null && !name.isEmpty()) {
wrapper.like("name", name);
}
return Result.ok(userService.list(wrapper));
}
@GetMapping("/{id}")
public Result<User> getById(@PathVariable Long id) {
User user = userService.getById(id);
if (user == null) {
return Result.fail("用户不存在");
}
return Result.ok(user);
}
@PutMapping("/{id}")
public Result<User> update(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
userService.updateById(user);
return Result.ok(user);
}
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.removeById(id);
return Result.ok(null);
}
}
这段代码已经是完整的项目核心逻辑了,覆盖了新增、查询列表、按 ID 查询、更新、删除五个高频操作。QueryWrapper 里 like 方法会自动处理 % 拼接,不用自己写 SQL。
5. 启动、验证与常见故障
5.1 启动流程与接口自测
工程代码写完后,在当前目录跑一条命令启动应用:
bash复制mvn spring-boot:run
如果项目还没依赖下载完,第一次启动会比较慢,Maven 会把所有依赖拉取到本地仓库。启动日志里看到类似 Started QuickStartApplication 这样的信息,说明应用已经起来了。
验证接口我用最简单的方式,curl 直接打:
bash复制curl -X POST http://localhost:8080/user \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com","age":18}'
正常返回应该带 code: 0 和新增后的用户数据。再去查询:
bash复制curl http://localhost:8080/user
控制台会打印 MyBatis-Plus 生成的 SQL,比如:
sql复制SELECT id,name,email,age,deleted,create_time FROM user WHERE deleted=0
看到 deleted=0 你能立刻确认逻辑删除生效了,查询接口没有把已删除的数据捞出来。
5.2 高频踩坑与排查思路
我在搭建过程中遇到了一些很典型的坑,这里集中整理一下,基本覆盖 90% 的新手问题:
| 现象 | 常见原因 | 解决方案 |
|---|---|---|
启动报 ClassNotFoundException: javax.sql.DataSource |
MyBatis-Plus starter 引错版本 | 换成 mybatis-plus-spring-boot3-starter |
启动报 Failed to configure a DataSource |
数据源连接信息缺失或错误 | 检查 application.yml 中的 URL、用户名、密码 |
连接数据库报 Public Key Retrieval is not allowed |
MySQL 8 默认认证插件问题 | JDBC URL 加上 allowPublicKeyRetrieval=true |
| 接口查不到数据 | 表名和实体类名对不上 | 检查 @TableName 注解 |
| save 报主键重复 | 主键策略配置不当 | 数据库自增就配 IdType.AUTO,不要全局覆盖 |
| 删除操作后数据还在 | 逻辑删除生效了 | 确认 deleted 字段被更新为 1,不是 bug |
| 中文乱码 | 表和连接编码不一致 | 确认数据库表为 utf8mb4,URL 带 characterEncoding=utf8 |
有个我印象很深的坑:Spring Boot 3 项目里手误用了老版 MyBatis-Plus starter,启动时确实不报错,但运行到第一个 Mapper 方法就抛异常,提示找不到 SqlSessionFactory 相关的 Bean。这种问题有时候不一定是依赖缺失,而是自动配置类根本没有被 Spring Boot 3 的条件注解认可。排查思路是去看启动日志里有没有 MybatisPlusAutoConfiguration 相关的字样,没有的话说明自动配置没生效,基本就是依赖坐标问题。
另外一个容易被忽略的点:mybatis-plus 的 GlobalConfig 中 db-config 配置项在不同版本里有细微差异,比如逻辑删除字段名 logic-delete-field 配置后,实体类里不是所有字段都一定要加 @TableLogic,全局配置会按照字段名匹配。我建议实体类上还是明确加注解,一个是为了醒目,另一个是避免字段名配置错误导致逻辑删除不生效。
6. 工程落地后还能怎么改进
6.1 分页插件与自动填充
接口跑通只是第一步,实际项目里分页基本是刚需。MyBatis-Plus 内置了物理分页插件,不过需要手动配置一个分页拦截器:
java复制package com.example.quickstart.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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;
}
}
配完分页插件后,Controller 里就能这样用:
java复制@GetMapping("/page")
public Result<Page<User>> page(@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size) {
Page<User> page = userService.page(new Page<>(current, size));
return Result.ok(page);
}
自动填充功能也很实用,比如每张表都有 create_time 和 update_time,不想在业务代码里手动 set。可以用 MyBatis-Plus 的 MetaObjectHandler 统一处理。
6.2 遇到超大规模项目怎么办
如果项目足够大,单表 CRUD 只是起步。这个时候 MyBatis-Plus 的优势会有所收敛,甚至会有团队觉得侵入性太强。我的建议是:如果你团队的规范非常严格,SQL 由 DBA 统一管控,那可能直接用原生 MyBatis 或者 JPA 更好。但大多数人遇到的项目本质是业务量大、单表操作多,MyBatis-Plus 在这类场景下确实是效率神器。
从工程演进角度来看,SpringBoot3 + MyBatis-Plus 非常适合作为微服务架构里单个服务的最小单元。一个服务一个数据库、一个数据源、一组 Mapper,按照本文的流程很快就能搭出下一个服务。后面再接入注册中心、配置中心、链路追踪,也只是在这个骨架上的上层建筑。
6.3 按需引入,不要过度封装
最后给一个我个人反复踩坑后总结出来的建议:不要为了“优雅”在工程里套太多过度封装。我看到过一些工程把 Service 层每个方法都包一层自定义返回结构,再配一套非常复杂的异常切面,表面上很完备,实际上新人接手完全看不懂。5 分钟能搭出来的工程,应该先保证路径通畅、代码直观,再考虑抽象和扩展。等到确实有多个服务、多个业务模块需要共享统一逻辑时,再抽公共模块也不迟。
我自己的习惯是:第 1 天搭骨架,第 2 天加统一异常处理,第 3 天才开始加通用分页、审计日志这些横向功能。搭建速度看起来慢了一点,但每一步的稳定性高了很多。这一点不管你是个人项目还是团队协作,都值得参考。
