做后端开发的,谁还没跟数据库连接打过架?我记得刚学 Spring Boot 那会儿,照着教程敲完代码,启动时连报三个错,一下午全耗在 MySQL 连接上。后来把 MyBatis-Plus 用顺了,才发现这些坑大多能一次避开。这篇就围绕“Spring Boot + MyBatis-Plus 快速连接 MySQL”这个主题,把从项目初始化、依赖配置、数据源连接,到实体类、Mapper、CRUD 接口跑通这一整条链路掰开揉碎讲清楚,适合刚入门 Spring Boot 的 Java 开发者,也适合想把持久层换成 MyBatis-Plus 的老手。你不用照着命令行一步步死记,重要的是理解每个环节为什么这么配,后面遇到问题才知道去哪排查。
1. 项目整体设计与技术选型思路
1.1 为什么持久层框架选 MyBatis-Plus
Spring Boot 项目里操作 MySQL,主流无非三条路:Spring Data JPA、原生 MyBatis、MyBatis-Plus。JPA 对复杂 SQL 和动态 SQL 不够友好,实体关系复杂之后性能不好控制;原生 MyBatis 灵活但样板代码多,写一个简单的单表 CRUD 要配 Mapper XML、ResultMap,工作量不小。MyBatis-Plus 是 MyBatis 的增强工具,只做增强不做改变,单表 CRUD 直接继承 BaseMapper 就有现成方法,不用写 SQL,复杂查询又能退回到 XML 或注解自己写,可以说兼顾了效率和灵活度。
我实际用下来最舒服的一点是,MyBatis-Plus 内置了条件构造器 Wrapper,像“根据姓名模糊查询、年龄大于多少、按时间倒序”这种动态 SQL,在 Java 代码里链式写就行,既不会拼 SQL 拼到怀疑人生,也能避免字符串条件拼接带来的 SQL 注入风险。这套东西在国内团队里普及率很高,新同事上手快,代码风格也统一。
1.2 Spring Boot、MyBatis-Plus、MySQL 版本怎么配
版本选型是新手容易忽略、但踩坑最多的地方。Spring Boot 2.7.x 搭配 MyBatis-Plus 3.5.x 和 MySQL 8.0,是目前最稳的组合,网上资料多,遇到问题基本都能搜到答案。如果你用的是 Spring Boot 3.x,那就要注意了——从 3.0 开始,Spring 全面拥抱 Jakarta EE,MyBatis-Plus 也单独出了 mybatis-plus-spring-boot3-starter,直接依赖旧版会缺包。
还有个坑是 MySQL 驱动版本。MySQL 8.0 之后的官方驱动建议用 com.mysql:mysql-connector-j,类名是 com.mysql.cj.jdbc.Driver。如果项目还写着老旧的 com.mysql.jdbc.Driver,连 8.x 数据库会直接报错。JDK 版本方面,Spring Boot 2.7 可以用 JDK 8 或 11,Spring Boot 3.x 要求 JDK 17 起步,这个也要提前确认好,别项目搭完了发现本机 JDK 版本不对。
1.3 数据库准备:本地安装还是 Docker 跑一个
连接 MySQL 之前,得先有一个能连的 MySQL 实例。这里两条路:一是官网下载 MySQL 安装包本地装,这也是热词里很多人搜“mysql安装教程”“mysql windows安装教程”的原因。Windows 下安装 MySQL 8 其实不难,下载 zip 包解压后,以管理员身份打开命令提示符,进入 bin 目录执行初始化命令,再启动服务就行,唯一要留意的是初始化时生成的临时密码,第一次登录必须改掉。
二是用 Docker 跑,我个人在开发环境更推荐这种方式,一条命令搞定,不用污染本机环境:
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=demo \
mysql:8.0
端口映射、root 密码、初始数据库都通过环境变量指定了,省心得很。日常连接数据库推荐用 MySQL Workbench 或 Navicat,建库建表看数据都方便。数据库准备这块,核心目标就是拿到一个能用的 IP、端口、账号、密码和库名,后面数据源配置全指望这几个参数。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工程搭建与核心配置
2.1 用 Spring Initializr 初始化项目
工程创建最快的方式是去 Spring Initializr 网站生成,也可以用 IDEA 内置的 Spring Initializr。这里有个小建议:坐标选 com.example 这种没问题,但包名别带中文和特殊符号,否则后面扫描会有一些奇怪问题。创建项目时先只勾选 Web 依赖,MyBatis-Plus 和 MySQL 驱动后面手工加,因为很多初始化器里根本没有 MyBatis-Plus 选项,它还不在 Spring 官方维护的依赖列表里。
用 IDEA 创建完项目之后,建议顺手把 maven-wrapper 相关文件检查一下,如果下载依赖很慢,就在 settings.xml 里配置阿里云镜像仓库。这一步看似跟连接数据库无关,但依赖拉不下来,后面所有代码都跑不起来,属于典型的“没出门先被门槛绊倒”。
2.2 依赖引入的先后与避坑
pom.xml 是整个工程的地基。Spring Boot 项目一定有父工程 spring-boot-starter-parent,版本号用 2.7.18 这类稳定版就行,不用刻意追新。核心依赖三个:spring-boot-starter-web 提供 Web 能力,mybatis-plus-boot-starter 引入 MyBatis-Plus,mysql-connector-j 是数据库驱动。
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</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>
</dependencies>
关于 MyBatis-Plus 的版本,3.5.3.1 是我用得比较多的版本,稳定且兼容性好。3.5.4 之后有一些 API 弃用提醒,不影响使用,但如果你强迫症不想看波浪线,也可以用 3.5.5。还有一个容易踩的坑:如果你同时引入 mybatis-plus 和 mybatis-plus-boot-starter,会出现重复类冲突。记得只用 boot starter 这一个就够了,它在内部已经包含了 MyBatis 核心依赖。
2.3 application.yml 数据源配置逐项拆解
Spring Boot 的数据库连接配置都写在 application.yml 里,核心是 spring.datasource 这一组配置。我直接给出一份完整可用的配置,下面逐项解释为什么这么写:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
driver-class-name 用 com.mysql.cj.jdbc.Driver,这是 MySQL 8.x 驱动的完整类名。url 里的参数是重点,逐个说:useUnicode=true&characterEncoding=utf8 保证中文不会乱码;serverTimezone=Asia/Shanghai 指定时区,不加这个,MySQL 8 连接时会报“The server time zone value”的错误;useSSL=false 是关闭 SSL 加密,本地开发足够用,省去证书配置的麻烦;allowPublicKeyRetrieval=true 这个是 MySQL 8 的经典问题,后面单独说。
mybatis-plus 前缀的配置是 MyBatis-Plus 自己的。map-underscore-to-camel-case: true 是让数据库的下划线字段自动映射成实体类的驼峰属性——比如数据库 created_at 字段,实体类写成 createdAt,不用手动指定映射关系。log-impl 配成 StdOutImpl 是让 SQL 打印到控制台,开发阶段务必开启,排查问题时能看到完整 SQL 和参数。id-type: auto 是全局的主键策略,配合数据库自增主键使用。
3. 从0到1打通一条 CRUD 链路
3.1 建库建表:SQL 脚本要设计好
数据源配置好了,接下来得有表可以操作。用 MySQL Workbench 或命令行连接到 MySQL,执行下面的建库建表脚本。这里我强烈建议字符集用 utf8mb4 而不是 utf8,因为 utf8mb4 是完整的 UTF-8 实现,能存储 emoji 和生僻字,MySQL 8 默认就是 utf8mb4,你不需要为兼容老版本而妥协。
sql复制CREATE DATABASE demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo;
CREATE TABLE `user` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
`name` VARCHAR(50) NOT NULL COMMENT '姓名',
`age` INT DEFAULT 0 COMMENT '年龄',
`email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT '用户表';
这里提前踩个坑:user 表其实是个“关键词”,在 MySQL 里不算是完全保留字,但有些工具和中间件里可能冲突。如果你在项目里用 @TableName("user") 没问题,但最好还是用 t_user、sys_user 这种更规范的表名。表名用下划线风格,字段名也用下划线风格,这是跟 MyBatis-Plus 驼峰映射配合最顺的方式。created_at 和 updated_at 这种字段,建议建表时就直接用数据库默认值维护,别让应用层手动塞时间。
3.2 实体类:MyBatis-Plus 注解的花样
表建好了,接下来在 Java 代码里建实体类。实体类说白了就是表和 Java 对象之间的映射,用 MyBatis-Plus 的时候不需要写 XML 映射文件,全是注解驱动。来看一个完整的实体类:
java复制@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@TableField("created_at")
private LocalDateTime createdAt;
@TableField("updated_at")
private LocalDateTime updatedAt;
}
@Data 是 Lombok 注解,自动生成 getter、setter、toString,代码干净不少。@TableName("user") 指定表名,跟数据库表对应。@TableId(type = IdType.AUTO) 标注主键,AUTO 表示数据库自增。@TableField 用来处理 Java 属性名和数据库字段名不一致的情况——createdAt 和 created_at 本可以通过驼峰映射自动对应,但写出来更明确,也防止以后改动全局配置影响这块。
实体类的字段类型,我建议日期都用 LocalDateTime 而不是 Date,这是 JDK 8 之后的新时间 API,配合 JSON 序列化、数据库映射都没什么兼容问题。BigInteger 和 Long 对主键来说都行,MySQL BIGINT 映射成 Long 是最自然的。
3.3 Mapper、Service、Controller 三层一把梭
MyBatis-Plus 最爽的部分来了。传统 MyBatis 要写接口、写 XML、配 ResultMap,现在直接继承接口就有了全套单表 CRUD。Mapper 层这样写:
java复制@Mapper
public interface UserMapper extends BaseMapper<User> {
}
就一行。没有 XML,没有 SQL,insert、deleteById、selectById、selectList、updateById 这些方法全在 BaseMapper 里预置好了。@Mapper 注解告诉 Spring 这个接口要生成代理实现,如果你在启动类上加了 @MapperScan("com.example.demo.mapper"),这里的 @Mapper 也可以省略,但建议还是写上,扫描路径配置错了容易排查。
Service 层同样有现成的模板类:
java复制public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
IService 和 ServiceImpl 提供了比 Mapper 更丰富的批量操作、链式查询方法,比如 saveBatch、lambdaQuery,省掉大量重复劳动。到了 Controller 层,可以快速写几个接口:
java复制@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public boolean save(@RequestBody User user) {
return userService.save(user);
}
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getById(id);
}
@GetMapping("/list")
public List<User> list() {
return userService.list();
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Long id) {
return userService.removeById(id);
}
}
注意 @RequestBody 接收 JSON,所以前端传数据时要设置 Content-Type: application/json。如果只是用表单提交或 URL 参数,那可以去掉 @RequestBody,直接用对象接收。
3.4 启动验证:命令行跑起来
代码都写完了,怎么验证?最直接的方式是在启动类里加一个 CommandLineRunner,项目启动完成后自动执行一段测试代码:
java复制@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private UserMapper userMapper;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) {
User user = new User();
user.setName("张三");
user.setAge(18);
user.setEmail("zhangsan@example.com");
userMapper.insert(user);
System.out.println(userMapper.selectList(null));
}
}
然后命令行运行,开发环境直接执行:
bash复制mvn spring-boot:run
或者先打包再运行:
bash复制mvn clean package -DskipTests
java -jar target/demo-0.0.1-SNAPSHOT.jar
跑起来之后在控制台看到 MyBatis-Plus 打印出的 INSERT 和 SELECT SQL,并且能输出查询结果,说明整条链路已经通了一半。再验证 HTTP 接口,浏览器访问 http://localhost:8080/user/list,或者用 Postman 发请求。这里如果发现端口被占用,可以在 application.yml 里改 server.port。
4. 高频报错与排查技巧实录
4.1 连接类报错速查表
我整理了一份实战中遇到过的连接类报错对照表,遇到问题先对号入座,基本都是这几类原因:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
Access denied for user 'root'@'localhost' |
用户名或密码错误 | 检查账号密码,确认 MySQL 里该用户存在 |
Unknown database 'demo' |
数据库名不存在或拼写错误 | 建库或修改 url 中的库名 |
Public Key Retrieval is not allowed |
MySQL 8 的 caching_sha2_password 认证机制 |
url 增加 allowPublicKeyRetrieval=true |
The server time zone value 'XXX' is unrecognized |
时区未指定 | url 增加 serverTimezone=Asia/Shanghai |
Connection refused |
MySQL 没启动、端口不对或防火墙拦截 | 检查 3306 端口监听状态 |
ClassNotFoundException: com.mysql.cj.jdbc.Driver |
驱动依赖没引入或版本不对 | 检查 pom.xml,确认驱动坐标和版本 |
这里重点说一下第一项,密码错误其实很常见,因为 Docker 部署 MySQL 时如果没设密码,默认允许空密码,本地装的 MySQL 又会生成一个临时密码。很多人拿着临时密码去连,自然报 Access denied。另一个隐蔽点:MySQL 8 里 root 用户的默认认证插件是 caching_sha2_password,老客户端默认不支持,这就是第四行那个报错的来源,url 加上 allowPublicKeyRetrieval=true 才能正常连接。
4.2 Public Key Retrieval is not allowed 与时区问题
这两个问题可以说是“新手必见双雄”。Public Key Retrieval is not allowed 的根源在于:MySQL 8 默认使用 caching_sha2_password 认证,第一次连接时需要通过 RSA 公钥交换密钥。出于安全考虑,MySQL 默认不允许客户端直接从服务器获取公钥,所以客户端驱动就抛了这个异常。解决方案就是上面说的,在 jdbc url 里明确告诉驱动允许获取公钥。生产环境安全性要求高的话,也可以把 MySQL 用户的认证插件改成 mysql_native_password,但新版本 MySQL 已经逐渐淘汰这个插件,不建议折腾。
时区问题比较烦人,因为报错信息可能五花八门,有的直接报 unrecognized,有的只是时间数据差 8 小时。根本原因是 MySQL 驱动 8.x 默认要求连接时指定时区,而我们的数据库是北京时间,应用服务器可能也是北京时间,但两者之间没有对齐信息。最简单的做法是统一在 jdbc url 里写死 serverTimezone=Asia/Shanghai,一句话解决,不用去改 MySQL 的全局时区配置。
4.3 依赖与启动类扫描的隐蔽坑
除了连接类的报错,还有两个开发中非常容易踩的坑。第一个是 Invalid bound statement (not found) 这条异常。很多人以为这是 MyBatis-Plus 的问题,其实八九成是 Mapper 接口没有被 Spring 扫描到。启动类上必须有 @MapperScan("com.example.demo.mapper"),或者每个 Mapper 接口上标注 @Mapper 注解。之前我带过一个小伙,启动类包名是 com.demo,Mapper 却放在 com.example.mapper 子包里,Spring 默认只会扫描启动类所在包及其子包,结果怎么都找不到 Bean。
第二个坑是实体类字段映射不上。数据库字段是 created_at,实体类属性是 createdAt,如果全局配置 map-underscore-to-camel-case 后依然查出来是 null,十有八九是 MyBatis-Plus 3.x 版本里的配置路径写错了。你要写 mybatis-plus.configuration.map-underscore-to-camel-case=true,注意这个缩进层级不能马虎。还有一种情况是 Lombok 忘装插件,IDEA 里没有启用 Annotation Processing,导致 @Data 注解不生效,实体类没有 getter/setter,MyBatis 反射也拿不到值,这种报错最摸不着头脑,排查半小时才发现是 IDE 设置问题。
4.4 连接池参数:上线前必须调的几项
Spring Boot 2.x 默认使用 HikariCP 连接池,这也是目前性能最强的连接池。很多人在本地开发没什么感觉,一到生产环境,并发一上来就报 connection timeout 或者 connection is not available,其实就是连接池参数没有调。
Hikari 的核心几个参数,我一般这样设:maximum-pool-size 根据应用的最大并发请求数来定,不是越大越好,我见过有人直接设 200,结果数据库连接数耗尽,MySQL 直接拒绝服务。一般单个节点 20 左右足够了,毕竟 MySQL 本身的默认最大连接数是 151。minimum-idle 是空闲连接数,可以跟 maximum-pool-size 一样,省去动态创建连接的延迟。connection-timeout 是等待连接的超时毫秒数,默认 30 秒太长,生产环境可以调到 3 到 5 秒,快速失败比排队死等更健康。max-lifetime 建议比数据库 wait_timeout 小一些,防止连接被数据库服务端断开后客户端还在用。
yaml复制spring:
datasource:
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 5000
idle-timeout: 600000
max-lifetime: 1800000
还有一个隐蔽问题是连接池里的 SQL 执行时间太长,导致连接被占满。遇到这种情况,优先查是不是有慢查询,给数据库加索引,而不是一味扩大连接池。
5. 让“快速”更进一步:分页、条件构造器与代码生成器
5.1 分页插件配置全流程
连接打通了,CRUD 会写了,但真实项目里不可能永远 selectList(null)。分页查询是后端接口的标配需求。MyBatis-Plus 的分页需要先注册一个拦截器 Bean,老版本用 PaginationInterceptor,3.5.x 之后统一用 MybatisPlusInterceptor 加 PaginationInnerInterceptor。很多新手只加了依赖没注册 Bean,直接调用分页方法,结果发现返回的全量数据,分页没生效,就是这个原因。
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
注册完成后,分页查询一行代码搞定:
java复制Page<User> page = new Page<>(1, 10);
Page<User> userPage = userMapper.selectPage(page, null);
long total = userPage.getTotal();
List<User> records = userPage.getRecords();
Page 的第一个参数是页码从 1 开始,第二个是每页条数。selectPage 方法会自动拼接 LIMIT,同时执行一条 COUNT 查询算出总数,很方便。这里有个细节:MyBatis-Plus 3.5.x 的分页插件会以 COUNT(*) 形式自动生成总数查询,如果你的 SQL 特别复杂,这个 count 可能效率不高,可以考虑后续手动优化。
5.2 条件构造器:QueryWrapper 和 LambdaQueryWrapper
条件构造器是 MyBatis-Plus 的灵魂功能。QueryWrapper 是字符串列名写法,容易因为表字段改名而报错。LambdaQueryWrapper 用方法引用,编译期就能发现字段名错了,推荐始终用 Lambda 写法:
java复制LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getAge, 18)
.like(StringUtils.isNotBlank(name), User::getName, name)
.orderByDesc(User::getCreatedAt);
List<User> users = userMapper.selectList(wrapper);
这段代码的逻辑是:年龄等于 18,姓名模糊匹配传入的 name,按创建时间倒序。有个常用技巧是 .like(condition, column, value) 这种重载,第一个参数是 boolean 条件,如果 name 为空就不拼这个条件,动态 SQL 就不用自己 if else 判断了。这样写出来既安全又流畅,不会像字符串拼接那样容易漏掉空格或引号。
实际工作中,我经常在 Controller 里直接接收查询参数,然后用条件构造器拼出查询条件再分页。这种用法非常普遍,强烈建议新手把这个 API 用熟,一天能省下不少时间。
5.3 代码生成器批量产出实体和 Mapper
连接配置、核心代码都跑通之后,如果项目里表特别多,一个个手写实体类、Mapper、Service 显然不划算。MyBatis-Plus 官方提供了一个代码生成器,3.5.x 版本用 FastAutoGenerator,核心配置几十行,能一次性根据数据库表生成实体、Mapper、Service、Controller 全套代码。这里我不展开全部配置,只提醒几个关键设置。
数据源连接信息要写对,跟 application.yml 一致就行。然后要设置全局策略,比如作者名、是否开启 Lombok、是否生成 Controller。生成之前确认 Url、Username、Password 这三个值没问题,否则第一步就连不上数据库。生成器最常用的做法是在测试类里跑一个 main 方法,执行一次就在指定包路径下生成文件,非常省事。不过生成器生成的代码只是骨架,复杂业务逻辑还是得自己补,别指望一劳永逸。
5.4 一点关于“快速”的补充:内嵌 Tomcat 与开发环境命令行
热词里有人搜“spring boot tomcat 部署”“spring boot项目 开发环境命令行运行项目”,这里简单补充一下。Spring Boot 的 spring-boot-starter-web 自带内嵌 Tomcat,所以本地开发甚至生产环境部署,默认都能直接 java -jar 跑起来。如果你要部署到外部 Tomcat,需要把打包方式改成 war,并且让启动类继承 SpringBootServletInitializer,但说实话现在大部分云原生部署场景,直接用可执行 jar 更省事。命令行运行项目我上面已经写了,mvn spring-boot:run 适合开发,mvn clean package 之后 java -jar 适合部署测试。第一次用 Maven 打包可能有点慢,主要是在下载依赖,之后就好了。
最后再分享一个我在实际开发中的小习惯:每次拿到新项目或者接手老项目,第一件事不是看代码,而是先确认数据库连接、Redis、日志这些基础配置能不能跑通。连接 MySQL 这件事看起来基础,但它是一整条链路的起点,任何一环出了问题,后面业务代码全是空中楼阁。这篇文章里所有配置和报错排查思路,都是我一次次实战中踩过的坑总结出来的,照着做不敢说百分之百顺利,但至少能帮你避开我当年花了一下午才绕出来的那些弯路。
