第一章: [基础] 快速入门与环境配置

第一章. [基础] 快速入门与环境配置

摘要:本章的目标是 用最快的速度搭建一个可以运行 Mybatis-Plus 的最小化 Spring Boot 3 项目。我们将聚焦于“生产级”的环境构建,不仅要跑通 Demo,更要从一开始就遵循阿里编码规约(Alibaba Java Coding Guidelines),掌握企业级表结构设计、依赖冲突解决以及 MP 核心组件的配置原理,为后续的高阶实战打下坚实地基。

本章学习路径

  1. 技术定调:深刻理解 Mybatis-Plus (MP) 相较于原生 MyBatis 的核心优势(不仅是少写 SQL,更是架构思维的转变)。
  2. 环境构建:分步构建基于 JDK 21 + Spring Boot 3.4 + MP 3.5.7 的现代化技术栈。
  3. 规约落地:创建符合大厂规范的数据库表结构(包含乐观锁、逻辑删除、审计字段)。
  4. 映射实战:通过“三步走”策略定义实体类,彻底掌握 @TableName@TableId 等核心注解。
  5. 启动验证:编写单元测试,跑通第一个“零 SQL”查询,验证环境闭环。

1.1. Mybatis-Plus 简介与核心优势

对于一位已经熟练掌握 Spring Boot 和原生 MyBatis 的开发者而言,MyBatis 的优点——完全掌控 SQL 的灵活性——毋庸置疑。但与此同时,其固有的开发痛点也同样突出。

痛点回顾:在原生 MyBatis 开发中,我们深受 样板代码(Boilerplate Code) 之苦。即便是一个最基础的单表“根据 ID 查询”功能,我们依然需要三步走:定义 Mapper 接口方法 -> 编写 XML 映射文件 -> 编写 SQL 语句。这种重复性劳动在项目初期和快速迭代中,会显著拖慢开发效率,且容易因拼写错误导致运行时异常。

为了更直观地展示这种差异,我们通过以下对比来感受 MP 的核心价值:

痛点:步骤繁琐,修改字段需同步修改 XML,维护成本高。

  1. Mapper 层:在 UserMapper 接口中定义方法。
    1
    User selectById(Long id);
  2. XML 层:在 UserMapper.xml 中手写 SQL。
    1
    2
    3
    <select id="selectById" resultType="com.demo.User">
    SELECT * FROM tb_user WHERE id = #{id}
    </select>
  3. Service 层:调用接口。

优势:零 SQL,零 XML,开箱即用。

  1. Mapper 层:让接口继承 BaseMapper,泛型指定实体类。
    1
    public interface UserMapper extends BaseMapper<User> {}
  2. XML 层完全不需要(针对基础 CRUD)。
  3. Service 层:直接调用继承自 IServiceBaseMapper 的现成方法。
    1
    2
    // 直接拥有 selectById, insert, update, deleteById 等 17+ 个通用方法
    User user = userMapper.selectById(id);

MP 的核心价值主张非常清晰:“只做增强,不做改变”。它完美继承了 MyBatis 的所有功能,并通过内置通用 MapperService,将我们从繁琐、重复的 CRUD 代码中彻底解放出来。这使得我们能更专注于复杂的业务逻辑,也正是我们称之为 MyBatis 最佳搭档 的根本原因。


1.2. 项目环境搭建

为了确保学习的先进性和稳定性,我们将采用 2025 年主流的 Spring Boot 3 + JDK 21 技术栈。

1.2.1. 技术栈版本说明

技术栈版本说明
JDK21LTS (长期支持版),虚拟线程的基础
Spring Boot3.4.x现代 Java 应用开发的事实标准
Mybatis-Plus3.5.7+完美适配 Spring Boot 3 的最新稳定版
MySQL Driver8.0.33官方推荐的 MySQL 8+ 驱动
Maven3.8+项目构建与依赖管理工具

1.2.2. Maven 依赖配置 (pom.xml)

我们将分三步来配置 pom.xml,以避免一次性堆砌代码造成的混乱。

第一步:父工程与核心属性

首先,我们需要指定 Spring Boot 的父工程,并定义版本号变量,以便于后续统一管理。

1
2
3
4
5
6
7
8
9
10
11
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.4</version>
</parent>

<properties>
<java.version>21</java.version>
<!-- 统一管理 MP 版本,方便日后升级 -->
<mybatis-plus.version>3.5.7</mybatis-plus.version>
</properties>

第二步:核心依赖引入

接下来,引入 MP、数据库驱动和常用工具库。

注意:在 Spring Boot 3.x 中,必须使用 mybatis-plus-spring-boot3-starter。如果错误引入了旧版的 mybatis-plus-boot-starter,会导致严重的依赖冲突(如 NestedIOException)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dependencies>
<!-- 1. Web 模块:提供 REST接口能力 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 2. MP 核心 Starter (Boot3 专用版) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

<!-- 3. MySQL 驱动 (Scope=runtime 表示编译时不需要,运行时才加载) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<!-- 4. Lombok:消除 Getter/Setter 样板代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- 5. 测试模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

第三步:构建插件

最后,添加 Maven 构建插件,确保项目能正确打包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- 打包时排除 Lombok,因为它在编译期已经生效,运行时不需要 -->
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

1.3. 数据源与 MP 基础配置

有了依赖,我们需要告知 Spring Boot 如何连接数据库,以及 MP 应该如何工作。我们推荐使用 YAML 格式,因为它层次分明。

1.3.1. 配置数据库连接

文件路径src/main/resources/application.yml

在配置数据源之前,请注意两个细节:

  1. 时区问题:URL 中必须指定 serverTimezone,否则读取到的时间可能与数据库相差 8 小时。
  2. 驱动类名:MySQL 8.x 推荐使用 com.mysql.cj.jdbc.Driver(带 cj)。
1
2
3
4
5
6
7
8
9
10
server:
port: 8080

spring:
datasource:
# 替换为你的本地数据库连接信息
url: jdbc:mysql://127.0.0.1:3306/mybatis_plus_notes?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver

1.3.2. 配置 MyBatis-Plus 行为

接下来,我们需要配置 MP 的核心行为。这里有两个至关重要的配置项:

  1. 驼峰映射:解决数据库字段 user_name 和 Java 属性 userName 命名不一致的问题。
  2. SQL 日志:开发阶段必须开启,只有看到实际生成的 SQL,我们才能真正掌握 MP 的运行逻辑。
1
2
3
4
5
6
7
8
9
mybatis-plus:
global-config:
# 关闭启动时的 MP Logo banner,保持控制台清爽
banner: false
configuration:
# [核心] 开启驼峰命名自动映射 (默认为 true,显式配置更安心)
map-underscore-to-camel-case: true
# [核心] 输出 SQL 日志到控制台 (生产环境请关闭)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.4. 核心文件创建:规约与映射

环境就绪后,我们将进入核心开发阶段。本节将严格遵循《阿里巴巴 Java 开发手册》,演示如何设计一张“企业级”的数据库表,并将其映射为 Java 实体。

1.4.1. 数据库表结构 (tb_user)

在真实的企业项目中,一张表不仅仅包含业务字段,还必须包含用于运维和审计的“标配字段”。

执行 SQL 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 创建数据库
CREATE DATABASE IF NOT EXISTS `mybatis_plus_notes` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `mybatis_plus_notes`;

-- 创建用户表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '姓名',
`age` int unsigned DEFAULT NULL COMMENT '年龄',
`email` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
-- [企业级特性 1] 乐观锁:解决并发更新覆盖问题
`version` int unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁版本号',
-- [企业级特性 2] 逻辑删除:数据只做标记不物理删除,保障数据可追溯
`is_deleted` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删;1-已删)',
-- [企业级特性 3] 审计字段:记录数据的生命周期
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

-- 插入测试数据
INSERT INTO `tb_user` (`id`, `name`, `age`, `email`) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com');

设计解读

  • unsigned:对于 ID、年龄、版本号等非负字段,使用无符号类型可以扩大数值范围。
  • gmt_create / gmt_modified:遵循阿里规约,使用 gmt (Greenwich Mean Time) 前缀,明确时间标准。
  • is_deleted:逻辑删除是防止误删数据的最后一道防线,MP 对此有原生支持。

1.4.2. 实体类 (UserDO.java)

实体类(Entity/DO)是数据库表在内存中的镜像。我们将分三步来编写这个类,逐步揭示 MP 注解的奥秘。

文件路径src/main/java/com/example/mpstudy/domain/UserDO.java

步骤一:基础映射与表名绑定

首先,我们需要解决类名 UserDO 与表名 tb_user 不一致的问题。

1
2
3
4
5
6
7
8
9
10
package com.example.mpstudy.domain;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data // Lombok 生成 getter/setter/toString
@TableName("tb_user") // [核心注解] 显式指定映射的表名
public class UserDO {
// ... 属性定义待续
}

步骤二:主键策略配置

主键是数据库的灵魂。MP 提供了多种主键生成策略,这里我们需要特别注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

/**
* 主键 ID
* type = IdType.AUTO:
* 因为我们的 MySQL 表定义了 AUTO_INCREMENT,所以必须配置为 AUTO。
* 如果不配置,MP 默认使用 ASSIGN_ID (雪花算法),会导致插入时 ID 类型不匹配或报错。
*/
@TableId(type = IdType.AUTO)
private Long id;

private String name;
private Integer age;
private String email;

步骤三:逻辑删除与审计字段映射

最后,我们处理那些“企业级特性”字段。MP 能够自动识别驼峰与下划线的转换(如 is_deleted -> isDeleted),但对于逻辑删除功能,我们需要添加特定注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.time.LocalDateTime;

/**
* 乐观锁版本号
*/
private Integer version;

/**
* 逻辑删除标志
* @TableLogic:
* 加上此注解后,执行 deleteById(1) 时,
* MP 不会发送 DELETE 语句,而是发送 UPDATE tb_user SET is_deleted=1 WHERE id=1
*/
@TableLogic
private Integer isDeleted;

/**
* 创建时间 (对应数据库 gmt_create)
*/
private LocalDateTime gmtCreate;

/**
* 修改时间 (对应数据库 gmt_modified)
*/
private LocalDateTime gmtModified;
}

1.5. Mapper 接口与启动扫描

这是最令人舒适的一步——我们只需要定义接口,剩下的交给 MP。

1.5.1. 创建 Mapper 接口

文件路径src/main/java/com/example/mpstudy/mapper/UserMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.mpstudy.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpstudy.domain.UserDO;

/**
* 继承 BaseMapper<T> 后,
* UserMapper 自动获得了 T (UserDO) 对应的所有基础 CRUD 方法
*/
public interface UserMapper extends BaseMapper<UserDO> {
// 暂时无需编写任何方法
}

1.5.2. 配置扫描路径 (@MapperScan)

定义了接口如果不告诉 Spring,容器是无法管理它们的。这是新手最容易遗漏的一步。

文件路径src/main/java/com/example/mpstudy/MpStudyApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.mpstudy;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
// [千万别忘] 扫描 Mapper 接口所在的包,否则启动会报 "UserMapper not found"
@MapperScan("com.example.mpstudy.mapper")
public class MpStudyApplication {

public static void main(String[] args) {
SpringApplication.run(MpStudyApplication.class, args);
}
}

1.6. 启动验证:Hello World

一切准备就绪,我们通过单元测试来验证环境是否通畅,并观察 MP 到底在背后做了什么。

文件路径src/test/java/com/example/mpstudy/MpStudyApplicationTests.java

我们编写一个简单的测试用例,查询所有用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootTest
class MpStudyApplicationTests {

@Autowired
private UserMapper userMapper; // 注入我们刚刚定义的接口

@Test
void testSelect() {
System.out.println(("----- 验证查询功能 ------"));

// selectList(null): 参数为 Wrapper 条件构造器,传 null 代表无条件查询所有
List<UserDO> userList = userMapper.selectList(null);

// 打印结果,验证数据
userList.forEach(System.out::println);
}
}

运行结果与日志分析

点击运行,观察控制台输出。你将看到如下神奇的日志:

1
2
3
4
5
6
7
----- 验证查询功能 ------
==> Preparing: SELECT id,name,age,email,version,is_deleted,gmt_create,gmt_modified FROM tb_user WHERE is_deleted=0
==> Parameters:
<== Columns: id, name, age, email, version, is_deleted, gmt_create, gmt_modified
<== Row: 1, Jone, 18, test1@baomidou.com, 1, 0, 2025-08-22 10:00:00, ...
<== Row: 2, Jack, 20, test2@baomidou.com, 1, 0, 2025-08-22 10:00:00, ...
<== Total: 2

关键现象解读

  1. SQL 自动生成:我们要么没写 SQL,要么没写 XML,但 MP 帮我们生成了标准的 SELECT ... 语句。
  2. 逻辑删除生效:请注意 SQL 的末尾自动拼接了 WHERE is_deleted=0。这证明我们在实体类上加的 @TableLogic 注解生效了,MP 自动过滤掉了被标记删除的数据。
  3. 驼峰自动映射:数据库的 gmt_create 成功填充到了实体类的 gmtCreate 字段中。

1.7. 本章总结与环境配置速查

1.7.1. 场景一:Spring Boot 3 集成 MP 标准配置

需求:在 JDK 17+ 环境下,快速集成 MyBatis-Plus。
方案:引入 boot3-starter 并配置基础 YAML。

代码模版

1
2
3
4
5
6
<!-- 1. pom.xml 关键依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
1
2
3
4
5
# 2. application.yml 关键配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射 (user_name -> userName)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境开启 SQL 日志

1.7.2. 场景二:主键策略选择

需求:根据数据库表设计,选择正确的 ID 生成策略。
方案:使用 @TableId 注解。

代码模版

1
2
3
4
5
6
7
// 场景 A:数据库 ID 为 auto_increment (适合单体应用)
@TableId(type = IdType.AUTO)
private Long id;

// 场景 B:数据库 ID 为 bigint,且无自增 (适合分布式系统,MP 默认策略)
@TableId(type = IdType.ASSIGN_ID) // 雪花算法生成 19 位 ID
private Long id;

1.7.3. 核心避坑指南

  1. 启动报错 UserMapper not found / NoSuchBeanDefinitionException

    • 现象:Spring 容器启动失败,提示找不到 Mapper Bean。
    • 原因:忘记在启动类上添加 @MapperScan,或者扫描包路径错误。
    • 对策:检查启动类注解:@MapperScan("com.你的包名.mapper")
  2. 插入报错 Data truncation: Out of range value

    • 现象:插入数据时报错,提示 ID 数值超出范围。
    • 原因:Java 实体类 ID 为 Long,数据库字段为 int,且未配置 @TableId(type = IdType.AUTO)。MP 默认生成 19 位雪花 ID,超出了 int 的存储范围。
    • 对策:修改数据库 ID 为 bigint,或在实体类显式指定 IdType.AUTO
  3. 报错 NestedIOException / 类加载错误

    • 现象:Spring Boot 3 启动报错,堆栈信息包含 ASM 或 CGLIB 错误。
    • 原因:引入了旧版的 mybatis-plus-boot-starter (适配 Boot 2.x)。
    • 对策:确保 pom.xml 引入的是 mybatis-plus-spring-boot3-starter