工作里经常要给别人搭项目脚手架,我自己的习惯是先搞一个能跑起来的最小工程,再往里填业务逻辑。SpringBoot3 + MyBatis-Plus 这个组合,基本已经是国内 Java 后端开发的事实标准了,尤其是中小规模项目、内部管理系统、接口服务,用它起步特别快。这篇文章我会把从零到能跑通 CRUD 的完整过程拆开讲清楚,包括环境准备、依赖版本怎么配才不踩坑、配置文件每一行是干什么的、代码要写哪些东西,最后附上我实际遇到过的问题排查记录,照着走一遍大概就是标题说的 5 分钟。
1. 项目整体设计思路:这个组合为什么能“快”
先说结论:SpringBoot 负责把 Web 容器、配置管理、依赖注入这些基础设施全自动搞定,MyBatis-Plus 负责把数据库操作简化到“不用写 SQL 也能 CRUD”的程度,两者叠加之后,你真正要动手写的只有实体类、Mapper 接口、Service 和一个极简的 Controller。
1.1 SpringBoot3 到底变了什么
SpringBoot3 最大的变化是底层基于 Spring Framework 6,强制要求 JDK 17 起步。它做了几件大事:
- 包名从
javax.*迁移到了jakarta.*,所以你在代码里 import 注解时要注意,javax.servlet已经变成jakarta.servlet,网上搜到的老教程里很多 import 都过时了。 - 大量自动配置类采用新的方式注册,很多第三方组件的旧版 starter 直接失效,这也是为什么 MyBatis-Plus 必须用专门适配 Boot3 的 starter 包。
- 对 GraalVM 原生镜像的支持更好,虽然一般项目用不上,但说明它在向云原生靠拢。
这些变化对普通开发者的直接感受就是:JDK 必须装 17 以上,Maven 的 compiler 插件要配好 release 版本,第三方库得确认它发布了适配 Boot3 的版本。所谓“快”,前提是你别在版本上卡住。
1.2 为什么数据层选 MyBatis-Plus 而不是 JPA 或原生 MyBatis
我自己三种方案都写过,说实话各有适用场景,但如果你要的是“快速搭建、能跑、好维护”,MyBatis-Plus 的优势很突出:
- 单表 CRUD 零 SQL:继承
BaseMapper<T>之后,selectById、insert、updateById、deleteById这些方法直接用,条件是QueryWrapper或LambdaQueryWrapper,写起来很像流式 API,可读性高。 - 分页插件是官方提供的,一个配置类搞定,不像 JPA 还要处理 Pageable 的坑。
- 代码生成器能根据表结构直接生成实体、Mapper、Service、Controller,配合模板引擎,整个项目骨架可以一键生成。
- 相比原生 MyBatis,省掉了大量 XML 文件;相比 JPA,又保留了 SQL 可控性,复杂查询还能自己写
@Select注解。
当然 JPA 在领域模型复杂的系统里有它的优势,但就“5 分钟搭起来”这个目标而言,MyBatis-Plus 绝对是最短路径。团队里如果有不熟悉 Spring Data 的成员,MyBatis-Plus 的上手成本也更低,查询条件看着像写代码而不是猜方法名。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与版本选型:这里最容易浪费 5 分钟
这个环节我多说几句,因为大量新手项目跑不起来,不是代码问题,是版本问题。这里给你一份我实测过的组合。
2.1 基础环境清单
| 组件 | 版本要求 | 说明 |
|---|---|---|
| JDK | 17 及以上 | SpringBoot3 硬性要求,装 21 也可以 |
| Maven | 3.6.3 及以上 | 建议 3.8.x,配置阿里云镜像加速 |
| IDE | IntelliJ IDEA 2022+ | 社区版够用,注意别用太老的版本 |
| MySQL | 5.7 或 8.0 | 本文例子用 8.0,驱动要选对 |
2.2 MyBatis-Plus 版本选择关键点
这是整个环境准备里最需要记住的一点:MyBatis-Plus 从 3.5.3 开始提供了专门适配 SpringBoot3 的 mybatis-plus-spring-boot3-starter,artifactId 和传统的 mybatis-plus-boot-starter 不一样。如果你在 Boot3 项目里用了旧 starter,直接启动报错 ClassNotFoundException。
我当前推荐用 3.5.5 或更新的版本,坐标长这样:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
注意:如果是 SpringBoot2 项目,用的还是老的 mybatis-plus-boot-starter。这就是为什么很多帖子里的代码复制过来跑不通,版本选型不对是首要原因。
MySQL 驱动也要注意,Boot3 自动配置要求驱动坐标是 com.mysql:mysql-connector-j,而老教程里写的 mysql:mysql-connector-java 已经废弃了。
3. 工程初始化:两步得到一个空项目
初始化有两种方式,我分别讲,推荐用第一种,效率更高。
3.1 用 Spring Initializr 在线生成
打开 Spring 官方的初始化页面(或者你 IDEA 里内置的 Spring Initializr),填写项目信息:
- Project:Maven
- Language:Java
- Spring Boot:3.x 版本(我用的 3.2.5)
- Group:比如
com.example - Artifact:比如
quick-start - Java:17
- Dependencies 里勾选:Spring Web、MySQL Driver、Lombok(可选)
点生成之后下载 zip,解压后用 IDEA 打开,注意 IDEA 会自动识别 Maven 工程。等到依赖下载完毕,先在 pom.xml 里加上 MyBatis-Plus 的依赖,然后就可以进行配置了。
这一步生成的工程自带 mvnw 包装器和 .gitignore,方便直接 git init。如果你在命令行下操作,可以把整个工程当成一个快速原型,后续需要就用代码生成器继续扩展。
3.2 手动创建 Maven 工程(可选)
如果你不想用在线生成,也可以自己建一个空的 Maven 项目,然后在 pom.xml 里填写所有内容。完整 pom 大致长这样:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>quick-start</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>quick-start</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
把这个 pom 放进项目根目录,IDEA 右侧 Maven 面板刷新一下,依赖就能拉下来。这里有个小经验:如果依赖下载慢,去 ~/.m2/settings.xml 里配阿里云镜像,否则 5 分钟的项目可能光下载就花了 10 分钟。
4. 配置文件与核心配置类:让项目认识数据库
项目生成后,默认的 application.properties 是空的,我习惯直接改成 application.yml,写起来层级更清晰。配置文件是连接数据库最关键的一步,内容不多但每行都有讲究。
4.1 application.yml 完整配置与逐项说明
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/quick_start?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: your_password
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
逐项解释一下:
driver-class-name:Boot3 时代 MySQL 8 的驱动类名还是com.mysql.cj.jdbc.Driver,这个基本不变。url:serverTimezone=Asia/Shanghai是为了避免时区报错,useSSL=false是本地开发常用的,allowPublicKeyRetrieval=true是 MySQL 8 连接时偶尔会遇到的 RSA 公钥问题,加上更稳。map-underscore-to-camel-case:数据库字段user_name自动映射到属性userName,这个必须开,否则实体里驼峰属性全查不出来。log-impl:控制台打印 SQL,开发阶段一定开,排查问题少走弯路。id-type: auto:主键自增。logic-delete-field:逻辑删除字段,后续业务一旦涉及删除操作,加上这个省很多事。
注意:如果你的数据库实例名、用户名密码和我写的不一样,记得改。许多人复制配置文件后忘了改密码,启动直接报连接超时。
4.2 分页插件配置类
MyBatis-Plus 的分页查询不是默认生效的,需要手动注册一个分页插件。忽略这一步的话,Page 查询返回的数据不分页,全表查出来,这在数据量大时是隐患。
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 paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
setMaxLimit(500L) 是给自己设的一道保险,防止手滑写了个巨大页码把数据库打垮。实际业务可以根据情况调整或去掉。
5. 核心操作与接口实现:用户 CRUD 完整示例
配置弄好,接下来就是写业务代码。我以一个 user 表为例,建表语句先放这里:
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`age` int DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`deleted` tinyint DEFAULT '0',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
对应它的工程代码,一共四层:实体、Mapper、Service、Controller。这是最标准的套路。
5.1 实体类与 Mapper 接口
实体类用 Lombok 简化 getter/setter:
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 Integer age;
private String email;
@TableLogic
private Integer deleted;
private LocalDateTime createTime;
}
@TableName 指定表名,防止实体名和表名不一致时映射错。@TableId(type = IdType.AUTO) 表示数据库自增主键。@TableLogic 配合配置里的逻辑删除字段,一旦配置,之后 deleteById 就会变成 update user set deleted=1 where id=?,不会真删数据。
Mapper 接口继承 BaseMapper 就有单表 CRUD 能力:
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> {
}
不用写任何 SQL。如果你以后有复杂查询,直接在这个接口里加方法,配合 XML 或注解即可。
5.2 Service 与 Controller
Service 同样继承 MyBatis-Plus 提供的 IService 和 ServiceImpl,把通用方法都拿到手:
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 {
}
Controller 用来接收请求,我写了一个极简版,涵盖查询、新增、修改、删除:
java复制package com.example.quickstart.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.quickstart.entity.User;
import com.example.quickstart.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getById(id);
}
@GetMapping("/list")
public List<User> list() {
return userService.list();
}
@GetMapping("/page")
public Page<User> page(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
Page<User> pageParam = new Page<>(page, size);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(User::getId);
return userService.page(pageParam, wrapper);
}
@PostMapping
public boolean save(@RequestBody User user) {
return userService.save(user);
}
@PutMapping
public boolean update(@RequestBody User user) {
return userService.updateById(user);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Long id) {
return userService.removeById(id);
}
}
这段代码里最值得学习的其实是 LambdaQueryWrapper。它用 Java 方法引用 User::getId 代替字符串字段名,编译期就能检查字段是否存在,不会出现写错列名导致运行时才报错的问题。我强烈建议所有简单查询都用它,而不是老式的 QueryWrapper。
分页接口设计上,前端传 page 和 size,返回的是 Page 对象,它里面包含 records(当前页数据)、total(总条数)、current(当前页)等属性,JSON 序列化后前端直接就能用,不需要二次封装。
6. 启动验证与常见问题排查记录
所有代码写完,启动项目主类,看到类似下面的日志就说明成功了:
code复制Tomcat started on port(s): 8080 (http)
Started QuickStartApplication in 2.3 seconds
接着浏览器或接口工具访问 http://localhost:8080/user/list,如果返回空数组,说明环境通、接口通、数据库连上了。
6.1 我踩过的高频问题速查表
| 现象 | 原因 | 解决方案 |
|---|---|---|
启动报 Invalid bound statement (not found) |
Mapper 接口没被扫描,或 XML 没匹配 | 启动类加 @MapperScan("com.example.quickstart.mapper");确认 XML 路径和 namespace 正确 |
启动报 Failed to configure a DataSource |
数据源配置缺失或连接串错误 | 检查 spring.datasource 四项配置,尤其是密码 |
| 查询结果全是 null | map-underscore-to-camel-case 没开启 |
yml 里配置开启;或者用 @TableField 手动映射 |
| 保存中文乱码 | 连接串没指定编码或数据库字符集不对 | URL 加 characterEncoding=utf8,确认表字符集是 utf8mb4 |
ClassNotFoundException: jakarta.servlet |
Boot3 用了旧版依赖 | 检查所有 starter 是否适配 Boot3,特别是 MyBatis-Plus 用 mybatis-plus-spring-boot3-starter |
| 分页不生效 | 没注册分页插件 | 加 MybatisPlusConfig,注册 PaginationInnerInterceptor |
No qualifying bean of type UserMapper |
启动类没扫描到 Mapper | 启动类上加上 @MapperScan 注解 |
这里特别强调 @MapperScan,很多人忘记写。不加这个注解,Spring 容器里就找不到 Mapper 代理对象,Service 注入直接失败。你也可以在每个 Mapper 接口上写 @Mapper,但项目一多,还是启动类扫描一劳永逸。
6.2 关于 5 分钟这件事的真相与心得
如果完全按我前面的步骤走,新建工程到接口能通,熟练的 3 分钟就够,不熟练的 10 分钟也很正常。关键在于不要在这个阶段纠结任何优化:不要写复杂的管理后台、不要做多数据源、不要上 Redis、不要分模块。先把一个最小的闭环跑通,这个“骨架”就稳稳立住了,后面加需求都是在上面长肉,而不是重新搭架子。
我个人在实际操作中的体会是:SpringBoot3 + MyBatis-Plus 这套组合最大的魅力不在于某个单独的技术点,而在于“约定大于配置”带来的连贯体验。一旦你理解了配置文件里那几项的含义,后面迁移到别的项目时,看别人的工程配置会非常快。还有一个小技巧:本地开发搭配一个数据库可视化管理工具,建表、造数据都很方便,不用在命令行里折腾 SQL 脚本,整个流程会更顺。最后建议你把这个最小工程提交到代码仓库作为一个模板,下次新项目直接复制一份改个名字,连 5 分钟都省了。
