适用版本 :MyBatis-Plus 3.5.3.1(最新稳定版)
适用场景 :Java SpringBoot 企业级开发
核心定位 :MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在 MyBatis 基础上只做增强不做改变,极简封装单表 CRUD、条件构造、分页、逻辑删除等企业常用功能,彻底消灭冗余 XML/注解 SQL,提升开发效率。
MyBatis-Plus 核心概述
是什么?
MyBatis-Plus 是由国内开发者开源的 MyBatis 增强框架,无侵入式扩展 MyBatis 功能,解决原生 MyBatis 冗余 CRUD、硬编码字段、分页繁琐等痛点。
核心优势
- 零 SQL 实现单表 CURD,无需手写 XML
- 强大的条件构造器,支持 Lambda 语法(无硬编码)
- 内置分页、逻辑删除、自动填充、乐观锁等企业功能
- 支持主键雪花算法、UUID 等分布式策略
- 代码生成器一键生成 Entity/Mapper/Service/Controller
与原生 MyBatis 对比
| 功能 | 原生 MyBatis | MyBatis-Plus |
|---|---|---|
| 单表 CRUD | 手写 SQL/XML | 继承接口直接用 |
| 条件查询 | 硬编码字段名 | Lambda 方法引用 |
| 分页 | 手动封装 | 内置插件 |
| 逻辑删除 | 手动写 SQL | 注解+配置 |
| 开发效率 | 低 | 极高 |
SpringBoot 整合 MyBatis-Plus
核心依赖
创建 SpringBoot 项目,引入核心依赖 (pom.xml):
xml
<!-- SpringBoot 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.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
数据库表(样例表)
创建企业常用的 user 用户表:
sql
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(32) NOT NULL COMMENT '用户名',
`age` int DEFAULT NULL COMMENT '年龄',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` tinyint DEFAULT '0' COMMENT '逻辑删除 0-未删除 1-已删除',
`version` int DEFAULT '0' COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
配置文件(application.yml)
yaml
spring:
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# MyBatis-Plus 配置
mybatis-plus:
configuration:
# 开启日志(开发环境查看执行 SQL)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 别名包扫描(实体类包)
type-aliases-package: com.example.mpdemo.entity
启动类配置
扫描 Mapper 接口包:
java
@SpringBootApplication
@MapperScan("com.example.mpdemo.mapper") // 扫描 Mapper 接口
public class MpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MpDemoApplication.class, args);
}
}
基础 CRUD 语法(BaseMapper)
核心逻辑
MP 提供 BaseMapper<T> 接口,内置17 个单表 CRUD 方法,继承后直接使用,无需手写 SQL。
步骤
- 编写实体类
- Mapper 接口继承
BaseMapper<实体类> - 直接调用 CRUD 方法
样例
实体类(User.java)
java
@Data // Lombok 生成 getter/setter/toString
@TableName("user") // 映射数据库表名(类名与表名一致可省略)
public class User {
// 主键(自增)
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private Integer age;
private String email;
private Date createTime;
private Date updateTime;
private Integer delFlag;
private Integer version;
}
Mapper 接口(UserMapper.java)
java
// 继承 BaseMapper,泛型指定实体类
public interface UserMapper extends BaseMapper<User> {
// 无需写任何方法,直接使用父类 CRUD
}
3.3.3 基础 CRUD 测试
java
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
// 1. 插入数据
@Test
void insert() {
User user = new User();
user.setUsername("张三");
user.setAge(20);
user.setEmail("zhangsan@qq.com");
// insert 返回影响行数
int rows = userMapper.insert(user);
System.out.println("插入行数:" + rows);
System.out.println("生成的主键:" + user.getId());
}
// 2. 根据 ID 查询
@Test
void selectById() {
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 3. 查询所有数据
@Test
void selectList() {
List<User> userList = userMapper.selectList(null); // null 表示无条件
userList.forEach(System.out::println);
}
// 4. 根据 ID 更新
@Test
void updateById() {
User user = new User();
user.setId(1L);
user.setAge(21); // 只更新年龄
userMapper.updateById(user);
}
// 5. 根据 ID 删除
@Test
void deleteById() {
userMapper.deleteById(1L);
}
}
BaseMapper 核心方法汇总
| 方法 | 作用 |
|---|---|
| insert(T entity) | 插入一条记录 |
| selectById(Serializable id) | 根据 ID 查询 |
| selectList(Wrapper queryWrapper) | 条件查询列表 |
| updateById(T entity) | 根据 ID 更新 |
| deleteById(Serializable id) | 根据 ID 删除 |
| selectBatchIds(Collection idList) | 批量 ID 查询 |
| delete(Wrapper queryWrapper) | 条件删除 |
条件构造器 Wrapper
核心逻辑
Wrapper 是 MP 的条件构造器抽象类,用于拼接 WHERE 条件,企业开发中最常用两个子类:
QueryWrapper<T>:查询/删除 条件构造UpdateWrapper<T>:更新 条件构造
常用条件方法
| 方法 | 含义 | 示例 |
|---|---|---|
| eq | 等于(=) | eq("age", 20) |
| ne | 不等于(!=) | ne("username", "张三") |
| gt | 大于(>) | gt("age", 18) |
| ge | 大于等于(>=) | ge("age", 18) |
| lt | 小于(<) | lt("age", 30) |
| le | 小于等于(<=) | le("age", 30) |
| like | 模糊查询(%值%) | like("username", "张") |
| between | 区间查询 | between("age", 18, 30) |
| orderByAsc | 升序排序 | orderByAsc("age") |
| isNull | 字段为空 | isNull("email") |
实战样例
QueryWrapper 查询
java
// 查询:年龄18-30岁、用户名包含"张"、邮箱不为空、按年龄升序
@Test
void queryWrapperTest() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 18, 30)
.like("username", "张")
.isNotNull("email")
.orderByAsc("age");
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
UpdateWrapper 更新
java
// 更新:将用户名=张三的用户,年龄改为22
@Test
void updateWrapperTest() {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("username", "张三"); // 条件
User user = new User();
user.setAge(22); // 更新字段
userMapper.update(user, wrapper);
}
缺点
硬编码数据库字段名 (如
"age"、"username"),容易写错且不易维护,企业开发禁止使用普通 Wrapper。
Lambda 条件构造器(推荐)
核心逻辑
解决普通 Wrapper 硬编码字段问题,使用 Lambda 方法引用 关联实体类字段,编译期校验,零错误。
核心子类:
LambdaQueryWrapper<T>:Lambda 查询LambdaUpdateWrapper<T>:Lambda 更新
实战样例(对比普通 Wrapper)
java
// Lambda 查询:年龄18-30、用户名含"张"、邮箱非空
@Test
void lambdaQueryTest() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.between(User::getAge, 18, 30)
.like(User::getUsername, "张")
.isNotNull(User::getEmail)
.orderByAsc(User::getAge);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
// Lambda 更新:用户名=张三 -> 年龄22
@Test
void lambdaUpdateTest() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getUsername, "张三");
User user = new User();
user.setAge(22);
userMapper.update(user, wrapper);
}
优势
- 方法引用关联实体字段,编译期报错(避免字段名写错)
- 代码可读性极高,符合企业开发规范
- 重构实体类字段时,自动同步修改
分页查询(必备)
核心逻辑
MP 内置分页插件 ,无需手写 limit,只需配置插件 + 传入 Page 对象,自动返回分页结果(总条数、总页数、当前页数据)。
步骤
- 配置分页插件
- 使用
selectPage(Page<T>, Wrapper)方法
实战样例
分页插件配置(MyBatisPlusConfig.java)
java
@Configuration
public class MyBatisPlusConfig {
/**
* 注册 MyBatis-Plus 插件(分页+乐观锁)
*/
@Bean
public MyBatisPlusInterceptor myBatisPlusInterceptor() {
MyBatisPlusInterceptor interceptor = new MyBatisPlusInterceptor();
// 1. 添加分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
// 设置数据库类型(MySQL)
paginationInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
分页查询测试
java
// 分页查询:第1页,每页3条,年龄>18,按ID降序
@Test
void pageTest() {
// 1. 创建分页对象:current=当前页,size=每页条数
Page<User> page = new Page<>(1, 3);
// 2. 条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.orderByDesc(User::getId);
// 3. 执行分页查询
Page<User> resultPage = userMapper.selectPage(page, wrapper);
// 4. 分页结果
System.out.println("当前页数据:" + resultPage.getRecords());
System.out.println("总条数:" + resultPage.getTotal());
System.out.println("总页数:" + resultPage.getPages());
System.out.println("当前页:" + resultPage.getCurrent());
System.out.println("每页条数:" + resultPage.getSize());
}
主键生成策略
核心逻辑
通过 @TableId(type = IdType.xxx) 指定主键生成策略,MP 提供 5 种主流策略,适配单机/分布式场景。
IdType 枚举详解
| 策略 | 含义 | 适用场景 |
|---|---|---|
| AUTO | 数据库自增(依赖表 AUTO_INCREMENT) | 单机单库 |
| ASSIGN_ID | 默认,雪花算法(Long 类型,全局唯一) | 分布式系统 |
| ASSIGN_UUID | UUID 字符串(无中划线) | 字符串主键 |
| INPUT | 手动输入主键 | 自定义主键 |
| NONE | 无策略,跟随全局 | 默认配置 |
实战样例
java
@Data
@TableName("user")
public class User {
// 分布式场景:雪花算法(默认可省略 type)
// @TableId(type = IdType.ASSIGN_ID)
// 单机场景:数据库自增
@TableId(type = IdType.AUTO)
private Long id;
}
常用字段映射注解
MP 提供注解解决实体类与数据库表映射不一致问题:
核心注解
-
@TableName:映射表名java@TableName("sys_user") // 类名User -> 表名sys_user -
@TableField:映射字段value:字段名(实体字段与表字段不一致时)exist = false:非数据库字段(实体类临时字段)fill:自动填充(创建/更新时间)
java@TableField(value = "user_name") // 实体username -> 表字段user_name private String username; @TableField(exist = false) // 非数据库字段,MP 忽略 private String tempData;
逻辑删除
核心逻辑
物理删除 :直接删除数据库记录(不可恢复)
逻辑删除 :更新 del_flag 字段(0=未删除,1=已删除),查询时自动过滤已删除数据,企业标准方案。
配置(application.yml)
yaml
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: delFlag
# 已删除值
logic-delete-value: 1
# 未删除值
logic-not-delete-value: 0
实体类注解
java
@TableLogic // 标记逻辑删除字段
private Integer delFlag;
测试效果
调用 deleteById(1L),执行的 SQL 是:
sql
UPDATE user SET del_flag=1 WHERE id=1 AND del_flag=0
调用 selectList(null),自动拼接 del_flag=0 条件。
自动填充(创建/更新时间)
核心逻辑
无需手动 setCreateTime()/setUpdateTime(),MP 自动在插入/更新时赋值,企业必备。
步骤
- 实体类标记填充策略
- 自定义元对象处理器
实战样例
实体类配置
java
@TableField(fill = FieldFill.INSERT) // 插入时填充
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入/更新时填充
private Date updateTime;
元对象处理器(MyMetaObjectHandler.java)
java
@Component // 交给 Spring 管理
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时填充
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
// 更新时填充
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
乐观锁(并发控制)
核心逻辑
解决并发更新数据覆盖 问题(如商品超卖),通过 version 版本号控制:
- 更新时携带版本号
- 版本号匹配才更新,同时版本号+1
- 不匹配则更新失败
配置
- 插件配置(已在分页插件中添加)
- 实体类标记
@Version
java
@Version
private Integer version;
Service 层封装
核心逻辑
MP 提供 IService<T> 接口 + ServiceImpl<M,T> 实现类,封装 Service 层 CRUD,无需手写基础方法。
实战样例
Service 接口
java
// 继承 IService,泛型=实体类
public interface UserService extends IService<User> {
// 自定义业务方法
}
Service 实现类
java
// 继承 ServiceImpl,泛型=Mapper+实体类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 直接使用父类方法:save、saveBatch、getById、list、page 等
}
12.2.3 Service 常用方法
java
@Autowired
private UserService userService;
// 批量插入
@Test
void saveBatch() {
List<User> list = new ArrayList<>();
list.add(new User(null, "李四", 25, "lisi@qq.com", null, null, 0, 0));
list.add(new User(null, "王五", 28, "wangwu@qq.com", null, null, 0, 0));
userService.saveBatch(list); // 批量插入
}
// 根据ID查询
@Test
void getById() {
User user = userService.getById(1L);
}
// 分页查询
@Test
void servicePage() {
Page<User> page = new Page<>(1, 3);
userService.page(page);
}
多表查询实现
MP 是单表增强框架 ,不推荐封装多表,企业开发多表查询用原生 MyBatis 手写 SQL:
样例:用户+订单联查
自定义 VO(返回结果)
java
@Data
public class UserOrderVO {
private Long userId;
private String username;
private Long orderId;
private String orderNo;
}
Mapper 接口方法
java
public interface UserMapper extends BaseMapper<User> {
// 多表查询:手写注解 SQL
@Select("SELECT u.id user_id, u.username, o.id order_id, o.order_no " +
"FROM user u LEFT JOIN `order` o ON u.id = o.user_id " +
"WHERE u.id = #{userId}")
UserOrderVO selectUserOrder(Long userId);
}
代码生成器(提升效率)
核心逻辑
MP 提供代码生成器,一键生成 Entity、Mapper、Service、Controller,企业开发提效 80%。
依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
生成器代码(直接运行)
java
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus_demo", "root", "123456")
// 全局配置
.globalConfig(builder -> {
builder.author("企业开发师") // 作者
.outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出路径
.enableSwagger() // 开启Swagger
.fileOverride(); // 覆盖文件
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example.mpdemo") // 父包名
.entity("entity")
.mapper("mapper")
.service("service")
.controller("controller");
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("user") // 需要生成的表名
// Entity 策略
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
// Mapper 策略
.mapperBuilder()
.enableBaseResultMap()
// Service 策略
.serviceBuilder()
.formatServiceFileName("%sService")
// Controller 策略
.controllerBuilder()
.enableRestStyle();
})
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
实践要义
- 统一使用 Lambda 条件构造器,禁止硬编码字段名
- 所有表必须加
create_time/update_time/del_flag,开启自动填充+逻辑删除 - 分页查询必须配置插件,禁止手写
limit - 分布式系统使用
ASSIGN_ID雪花算法主键 - 多表查询手写原生 SQL,不依赖 MP 封装
- 批量操作使用
Service层的saveBatch/updateBatchById - 生产环境关闭 SQL 日志,仅保留错误日志
- 禁止无条件调用
update/delete方法(避免全表更新/删除)
总结
零SQL实现单表 CRUD,大幅减少冗余代码Lambda条件构造器解决硬编码痛点,提升代码质量- 内置逻辑删除、自动填充、分页等企业功能,开箱即用
- 代码生成器+Service 封装,极致提升开发效率