Spring Boot整合MyBatis-Plus实战:简化CRUD操作的最佳实践
-
- [一、 前言](#一、 前言)
- [二、 核心概念与优势](#二、 核心概念与优势)
-
- [2.1 什么是 MyBatis-Plus?](#2.1 什么是 MyBatis-Plus?)
- [2.2 为什么选择它?(与传统 MyBatis 对比)](#2.2 为什么选择它?(与传统 MyBatis 对比))
- [三、 整合架构与工作原理](#三、 整合架构与工作原理)
-
- [3.1 整体架构图](#3.1 整体架构图)
- [3.2 核心机制解析](#3.2 核心机制解析)
- [四、 Spring Boot 实战整合步骤](#四、 Spring Boot 实战整合步骤)
-
- [4.1 环境准备](#4.1 环境准备)
- [4.2 引入依赖](#4.2 引入依赖)
- [4.3 数据源配置](#4.3 数据源配置)
- [4.4 编写实体类](#4.4 编写实体类)
- [4.5 创建 Mapper 接口](#4.5 创建 Mapper 接口)
- [4.6 测试 CRUD 操作](#4.6 测试 CRUD 操作)
- [五、 高级特性实战:条件构造器](#五、 高级特性实战:条件构造器)
-
- [5.1 Wrapper 体系结构](#5.1 Wrapper 体系结构)
- [5.2 常用条件实战](#5.2 常用条件实战)
- [5.3 LambdaWrapper:类型安全的最佳实践](#5.3 LambdaWrapper:类型安全的最佳实践)
- [六、 生产级必备功能](#六、 生产级必备功能)
-
- [6.1 分页插件](#6.1 分页插件)
- [6.2 逻辑删除](#6.2 逻辑删除)
- [6.3 自动填充](#6.3 自动填充)
- [七、 最佳实践与总结](#七、 最佳实践与总结)
-
- [7.1 实战建议](#7.1 实战建议)
- [7.2 总结](#7.2 总结)
Spring Boot 整合 MyBatis-Plus
- 核心概念与优势 2. 架构与工作原理 3. 实战整合步骤 4. 高级特性实战 5. 性能与生产级配置 无侵入性
通用CRUD
代码生成器
CRUD接口
Inject SQL注入器
反射与插件机制
依赖引入
数据源配置
实体类注解
Mapper接口
条件构造器
分页插件
逻辑删除
SQL拦截分析
字段自动填充
一、 前言
在企业级 Java 开发中,持久层框架的选择至关重要。MyBatis 凭借其灵活的 SQL 编写能力和动态 SQL 机制,长期占据主导地位。然而,随着业务的复杂化,单表 CRUD(增删改查)操作占据了大量的开发时间,且存在大量重复代码。
MyBatis-Plus(简称 MP) 正是为了解决这一痛点而生。它并没有改变 MyBatis 的核心,而是在其基础上做了增强,只做增强不做改变。本文将结合实战案例,深入探讨如何在 Spring Boot 中高效整合 MyBatis-Plus,实现 CRUD 操作的极致简化。
二、 核心概念与优势
2.1 什么是 MyBatis-Plus?
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2.2 为什么选择它?(与传统 MyBatis 对比)
| 特性 | 传统 MyBatis | MyBatis-Plus |
|---|---|---|
| 单表 CRUD | 需手写 Mapper XML 或注解,代码量大 | 内置通用 Mapper,零 XML,开箱即用 |
| SQL 构建 | 手写复杂的 WHERE 条件 | 链式 QueryWrapper,Java 代码构建 SQL |
| 主键策略 | 手动配置或编写拦截器 | 支持多种策略(雪花ID、自增等),配置即用 |
| 分页 | 需手写 count 查询和 limit 拼接 | 内置分页插件,自动 count 和分页 |
| 代码生成 | 依赖第三方工具(如 Generator) | 强大代码生成器,可根据表结构生成 Entity/Mapper/Service/Controller |
三、 整合架构与工作原理
在实战之前,理解其工作原理能让我们更好地避坑。
3.1 整体架构图
MyBatis-Plus 内部机制
继承
注入
动态构建 SQL
反射获取实体信息
Controller 层
Service 层
Mapper 层接口
BaseMapper
MyBatis-Plus 核心
SqlSession
数据库 JDBC
Entity 实体类
ISqlInjector
SQL注入器
AbstractSQL
SQL构建器
3.2 核心机制解析
- SQL 注入器 :
MyBatis-Plus 并没有取代 MyBatis,而是在启动时,通过扫描继承了BaseMapper的接口,自动将通用的 CRUD SQL(Select、Insert、Update、Delete)"注入"到 MyBatis 的 MappedStatement 中。 - 反射与表映射 :
当你调用insert(user)时,MP 会通过反射解析User类(实体类)的注解(如@TableName、@TableId),将其与数据库表结构进行映射,动态生成 SQL 语句。
四、 Spring Boot 实战整合步骤
我们将构建一个简单的用户管理系统,实现对用户表的 CRUD 操作。
4.1 环境准备
- JDK 1.8+
- Maven 3.5+
- MySQL 5.7+ / 8.0+
- Spring Boot 2.x / 3.x
4.2 引入依赖
在 pom.xml 中引入核心依赖。注意:引入 mybatis-plus-boot-starter 后,不要 再引入 mybatis-spring-boot-starter,以免冲突。
xml
<dependencies>
<!-- Spring Boot Web 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok (用于简化实体类 Getter/Setter) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
4.3 数据源配置
在 application.yml 中配置数据库连接信息。这里建议配置 SQL 日志输出,方便调试。
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: password
# MyBatis-Plus 配置
mybatis-plus:
configuration:
# 开启驼峰命名转换,如数据库字段 user_name 自动映射给 userName
map-underscore-to-camel-case: true
# 日志实现,控制台打印 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 全局主键类型 (AUTO:数据库自增, ASSIGN_ID:雪花算法)
id-type: auto
# 逻辑删除全局值(配置后,下面会讲)
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
4.4 编写实体类
创建数据库表对应的实体类 User。
java
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data // Lombok 生成 Getter/Setter
@TableName("sys_user") // 指定表名,如果类名与表名一致可省略
public class User {
/**
* 主键策略:
* IdType.AUTO -> 数据库自增
* IdType.ASSIGN_ID -> 雪花算法生成 ID (String 或 Long 类型)
* IdType.ASSIGN_UUID -> UUID
*/
@TableId(type = IdType.AUTO)
private Long id;
// 如果字段名与列名一致,可不加 @TableField
private String name;
private Integer age;
private String email;
/**
* 逻辑删除字段
* 标记逻辑删除:删除操作变为 UPDATE deleted=1
* 查询操作自动加 WHERE deleted=0
*/
@TableLogic
private Integer deleted;
/**
* 自动填充策略:插入时自动填充该字段
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
4.5 创建 Mapper 接口
这是 MyBatis-Plus 最神奇的地方。我们不需要编写 XML 文件,甚至不需要写任何方法定义,只需要继承 BaseMapper。
java
package com.example.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.entity.User;
import org.apache.ibatis.annotations.Mapper;
// 指定这是一个 Mapper 接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
// BaseMapper 中已经包含了基本的 CRUD 方法
// 这里可以定义自定义的复杂 SQL 方法
}
4.6 测试 CRUD 操作
编写单元测试,验证基础功能。
java
package com.example.mp;
import com.example.mp.entity.User;
import com.example.mp.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
class MpDemoApplicationTests {
@Autowired
private UserMapper userMapper;
// 1. 插入操作
@Test
public void testInsert() {
User user = new User();
user.setName("张三");
user.setAge(25);
user.setEmail("zhangsan@example.com");
// ID 会根据策略自动回填
int result = userMapper.insert(user);
System.out.println("影响行数: " + result + ", ID: " + user.getId());
}
// 2. 根据 ID 更新
@Test
public void testUpdateById() {
User user = new User();
user.setId(1L);
user.setName("李四");
// 注意:更新时如果属性为 null,该字段不会被更新(动态 SQL)
int result = userMapper.updateById(user);
System.out.println(result);
}
// 3. 根据 ID 查询
@Test
public void testSelectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 4. 批量查询
@Test
public void testSelectBatchIds() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
// 5. 条件查询
@Test
public void testSelectByMap() {
// Map 中的 key 是数据库列名,不是 Java 属性名
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "李四");
columnMap.put("age", 25);
List<User> users = userMapper.selectByMap(columnMap);
users.forEach(System.out::println);
}
// 6. 根据 ID 删除
@Test
public void testDeleteById() {
// 如果配置了逻辑删除,这里实际执行的是 UPDATE SET deleted=1
int result = userMapper.deleteById(1L);
System.out.println(result);
}
}
五、 高级特性实战:条件构造器
在实际业务中,查询条件往往是动态且复杂的。MyBatis-Plus 提供了强大的 Wrapper(条件构造器),让我们用 Java 代码构建 SQL,避免 XML 拼接。
5.1 Wrapper 体系结构
Wrapper 条件构造器
QueryWrapper
UpdateWrapper
LambdaQueryWrapper
LambdaUpdateWrapper
用于查询构造条件
用于更新构造条件
支持 Lambda 表达式
防止字段名写错
支持 Lambda 表达式
防止字段名写错
5.2 常用条件实战
java
@Test
public void testWrapper() {
// 1. 创建 QueryWrapper 对象
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 2. 构建查询条件
// 名字包含 "张"
queryWrapper.like("name", "张");
// 年龄在 20 到 30 之间
queryWrapper.between("age", 20, 30);
// 邮箱不为空
queryWrapper.isNotNull("email");
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
// 3. 模拟组装 SQL: SELECT id,name,age,email... FROM sys_user
// WHERE name LIKE %张% AND age BETWEEN 20 AND 30 AND email IS NOT NULL
}
5.3 LambdaWrapper:类型安全的最佳实践
硬编码字符串 "name" 容易出错,且重构时无法自动修改。推荐使用 LambdaQueryWrapper。
java
@Test
public void testLambdaWrapper() {
LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
// 使用 :: 的方式引用方法,直接指向 User 类的属性,安全且高效
lambdaQuery.like(User::getName, "王")
.lt(User::getAge, 40); // lt = less than (小于)
List<User> users = userMapper.selectList(lambdaQuery);
}
六、 生产级必备功能
6.1 分页插件
物理分页是企业开发标配。MP 的分页插件通过 SQL 拦截实现,支持 MySQL、PostgreSQL、Oracle 等多种数据库。
步骤 1:配置插件
java
package com.example.mp.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 {
/**
* 配置 MP 拦截器链
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
// 指定数据库类型,例如 DbType.MYSQL, DbType.ORACLE
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作,true 调回首页,false 继续请求
pageInterceptor.setOverflow(false);
// 单页分页条数限制,默认无限制
pageInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
步骤 2:使用分页对象
java
@Test
public void testPage() {
// 第 1 页,每页 2 条
Page<User> page = new Page<>(1, 2);
// 分页查询结果会自动封装回 page 对象中
userMapper.selectPage(page, null);
System.out.println("当前页数据: " + page.getRecords());
System.out.println("总条数: " + page.getTotal());
System.out.println("总页数: " + page.getPages());
// SQL: SELECT * FROM sys_user LIMIT 0,2
// 注意:MP 会先执行 SELECT COUNT(*) 查询总数
}
6.2 逻辑删除
业务开发中,我们通常不进行物理删除(DELETE),而是采用逻辑删除(标记为 deleted)。
- 配置方法见 4.3 节(
logic-delete-field等)和 4.4 节(@TableLogic)。 - 效果 :
- 调用
removeById时,SQL 变为UPDATE sys_user SET deleted=1 WHERE id=?。 - 调用
selectList时,SQL 自动带上WHERE deleted=0。 - 注意:如果在 XML 中手写了 SQL,需要手动处理逻辑删除字段,插件不会干预自定义 XML 的 SQL。
- 调用
6.3 自动填充
类似于数据库的 DEFAULT CURRENT_TIMESTAMP,但在 Java 端控制更加灵活,适合处理 create_time、update_time 等公共字段。
步骤 1:实现元对象处理器接口
java
package com.example.mp.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时自动填充
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
七、 最佳实践与总结
7.1 实战建议
- 字段命名 :数据库字段尽量使用
user_name下划线风格,Java 实体使用userName驼峰风格,利用 MP 自带的驼峰转换,省去大量@TableField注解。 - Wrapper 滥用 :虽然
Wrapper很方便,但如果涉及极其复杂的 SQL(如多表关联、Group By 复杂聚合),建议回退到 MyBatis XML 编写,保持代码可读性。MP 允许两者共存。 - 主键选择 :
- 分布式系统或需要分库分表时,推荐使用
IdType.ASSIGN_ID(雪花算法)。 - 单体应用且不涉及数据迁移,使用
IdType.AUTO(数据库自增) 更直观。
- 分布式系统或需要分库分表时,推荐使用
7.2 总结
Spring Boot 整合 MyBatis-Plus 极大地提升了后端开发效率,将开发者从繁琐的单表 SQL 中解放出来。通过 BaseMapper、ConditionWrapper 和分页插件,我们可以在几分钟内构建出一个功能完善的持久层。
技术演进路线:
JDBC -> Hibernate/JPA (全自动,但控制力弱) -> MyBatis (半自动,灵活但繁琐) -> MyBatis-Plus (灵活与效率的完美平衡) 。
掌握 MyBatis-Plus,不仅是掌握了工具,更是理解了"不重复造轮子"和"约定优于配置"的高级工程哲学。希望本文能助你在实际开发中如虎添翼。