做后端开发这些年,Spring Boot 与 MyBatis 的组合是我在项目里用得最多的一套持久层方案。接触过不少团队,最后大多会选择 MyBatis:SQL 自己写、结果映射可控、动态 SQL 灵活,在复杂业务查询和报表类需求里尤其顺手。这篇文章围绕“Spring Boot 集成 MyBatis”展开,从初始化配置、核心语法、缓存机制、日志打印到常见报错排查,结合我已经踩过的坑逐个拆解。不管你是刚开始接触 Spring Boot 的新人,还是已经有几年开发经验、想系统梳理 MyBatis 细节的朋友,都能从这里拿到可以直接落地的方案。
1. 内容整体设计与思路拆解
1.1 为什么是 Spring Boot + MyBatis,而不选 JPA
先说选型。MyBatis 本质上是半自动 ORM,SQL 由开发者自己把控,框架只负责参数映射、结果集映射、SQL 执行和缓存。JPA/Hibernate 是全自动 ORM,可以自动生成大量 SQL,但遇到复杂多表关联、分页统计、多条件组合这些场景时,要么写 JPQL,要么原生 SQL,要么直接走 EntityManager,调试起来会比较绕。
我见过不少项目引入 JPA 后,开发者自己写 JOIN 查不出来问题,日志里看不出来 SQL 对不对,最后被迫回到写原生 SQL。而 MyBatis 的 XML 文件结构直观,一条 SQL 就是一条 SQL,DBA 审起来也方便。比如一个高校实验室预约管理系统,核心业务通常涉及:
- 用户与权限表
- 实验室、仪器设备表
- 预约记录表
- 审批记录表
- 分时段状态统计
这种系统天然适合用 MyBatis,因为预约查询往往带日期范围、状态、实验室类别等各种条件组合,SQL 可控性就是最大的优势。
需要提醒的是,这里说的 MyBatis 是指原生 MyBatis。现在 MyBatis-Plus、MyBatis-Flex 也都很流行,它们在 MyBatis 基础上封装了单表 CRUD、逻辑删除等能力。我认为团队的技术偏好和项目生命周期是选型关键:单表居多可以选 MyBatis-Plus 提升效率;复杂 SQL、多数据源、对 SQL 精细控制要求高的项目,原生 MyBatis 反而更稳定。两者并不互斥,后面会专门讲区别。
1.2 一个贯穿全文的小案例
为了让内容不空泛,我假设一个接近真实业务的需求:实验室预约记录综合查询。页面传入用户姓名、实验室名称、预约状态、开始日期、结束日期,后端返回预约列表并支持分页排序。这个案例会覆盖参数传入、动态 SQL、resultMap 映射、分页和缓存排查,基本能代表生产环境里最常见的 MyBatis 使用形态。
工程默认结构如下:
- Spring Boot 2.7.x(实际项目如果是 3.x,集成方式不变,但 starter 版本需要对应升级)
- Maven 构建
- MySQL 8.x
- mybatis-spring-boot-starter
- Lombok 辅助实体类
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工程初始化与 Spring Boot 自动装配的关键配置
2.1 版本矩阵与依赖选择
Spring Boot 与 MyBatis Starter 的版本适配很关键。早期踩过一次坑:Spring Boot 3.0 出来后,我还继续用 mybatis-spring-boot-starter 2.x,结果启动直接报 ClassNotFound。原因是 Starter 2.x 默认依赖的是 Spring 5 和 Jakarta EE 8 之前的包,Spring Boot 3 全面迁移到了 Jakarta EE 9。
我这里给出当前比较稳定的搭配:
| 环境 | 版本建议 |
|---|---|
| JDK | 8 / 11 / 17 |
| Spring Boot | 2.7.14 或 3.x 对应版本 |
| mybatis-spring-boot-starter | Spring Boot 2.x 使用 2.3.x,Spring Boot 3.x 使用 3.0.x |
| MySQL Connector/J | 8.0.33 |
| 连接池 | HikariCP(Spring Boot 默认) |
Maven 依赖的核心部分长这样:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.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>
如果你是在 2024 年之后新建 Spring Boot 3 项目,直接找 mybatis-spring-boot-starter 下的 3.x 版本即可。因为 Starter 的坐标没变,所以代码层面大多可以无缝迁移。
2.2 配置文件的推荐写法
这是我项目里沿用至今的一套精简配置:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/lab_booking?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
hikari:
maximum-pool-size: 20
minimum-idle: 5
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.labbooking.entity
configuration:
map-underscore-to-camel-case: true
jdbc-type-for-null: "null"
几个容易混淆的细节:
mapper-locations用来声明 XML 文件路径。如果写的是classpath:mapper/*.xml,那么 XML 文件必须放在src/main/resources/mapper目录下。type-aliases-package是一个偷懒配置,声明之后,XML 里的resultType可以直接写实体类短类名User,不用写全限定名。map-underscore-to-camel-case: true表示把数据库中的create_time自动映射到实体的createTime。这是大多数人容易漏的一条,漏了以后列表接口能查出来,但字段全是 null,排查起来比较头疼。jdbc-type-for-null在部分数据库插入 null 值时报“无效的列类型”时可以辅助解决。
接着看启动类。两件事:加 @MapperScan 扫描 Mapper 接口。也可以不在启动类加,而在每个 Mapper 接口上加 @Mapper。两种方式都能用,但在大型项目里,我建议启动类统一 @MapperScan,避免漏注解。
java复制@SpringBootApplication
@MapperScan("com.example.labbooking.mapper")
public class LabBookingApplication {
public static void main(String[] args) {
SpringApplication.run(LabBookingApplication.class, arg
