Why Mybatis-plus:
- 少写 80% 重复代码:单表 CRUD 不用写 XML / 注解 SQL,继承 BaseMapper 直接调用,新增、查询、更新、删除一行代码搞定,告别 "复制粘贴" 式开发;
- 动态 SQL 不用拼 :LambdaQueryWrapper 链式调用,条件查询像 "说话" 一样直观(比如
ge(User::getAge, 18).like(User::getUserName, "张")),杜绝字段拼写错误和<if>/<where>标签繁琐配置;- 分页一键搞定 :内置物理分页插件,不用集成第三方工具,
new Page(pageNum, pageSize)+selectPage()直接返回分页结果,性能比内存分页更优;- 代码生成省时间:AutoGenerator 一键生成 Entity、Mapper、Service、Controller 全套代码,新模块开发周期直接缩短 30%,不用手动建文件写模板;
- 常用功能内置化:逻辑删除、乐观锁、主键策略、自动填充(创建时间 / 更新时间),不用手动写 SQL 控制,注解 + 简单配置就能实现,解决并发、数据安全等常见问题;
- 零侵入兼容 MyBatis:完全保留 MyBatis 灵活性,复杂 SQL(多表联查、子查询)还能写 XML,既享受高效开发,又不牺牲自定义扩展能力;
快速上手:
1引入依赖:
java
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
由于这个starter包含对mybatis的自动装配,因此完全可以替换掉Mybatis的starter。
2定义Mapper
为了简化单表CRUD,MybatisPlus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD:

因此我们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了。
例如:(需要指定BaseMapper需要操作的实体类类型)
java
package com.itheima.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
public interface UserMapper extends BaseMapper<User> {
}
测试:
之后就能使用BaseMapper里已经拥有的丰富的增删改查功能:
java
package com.itheima.mp.mapper;
import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = new User();
user.setId(5L);
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
@Test
void testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
@Test
void testSelectByIds() {
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));
users.forEach(System.out::println);
}
@Test
void testUpdateById() {
User user = new User();
user.setId(5L);
user.setBalance(20000);
userMapper.updateById(user);
}
@Test
void testDelete() {
userMapper.deleteById(5L);
}
}
mp的默认规则:
MybatisPlus是根据BaseMapper中泛型PO实体(上面测试中的是User类)的信息来推断出表的信息,从而生成SQL的,我们想利用mp快速生成sql,就要遵守他的默认规则
默认情况下:
-
MybatisPlus会把PO实体的类名驼峰转下划线作为表名
-
MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
-
MybatisPlus会把名为id的字段作为主键
注意:但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。
常用注解
**@TableName:**标识实体类对应的表
示例:
java
@TableName("user")
public class User {
private Long id;
private String name;
}
TableName除了标识表名,还有其他属性:

**@TableId:**标识实体类中的主键字段
示例:
java
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
}
属性:

其中IdType常见类型:
-
AUTO:利用数据库的id自增长 -
INPUT:手动生成id -
ASSIGN_ID:雪花算法生成Long类型的全局唯一id,这是默认的ID策略
**TableField:**普通字段注解:标识数据库中对应的字段
示例
java
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
@TableField(is_married")
private Boolean isMarried;
@TableField("`concat`")
private String concat;
}
注意:一般情况下我们并不需要给字段添加@TableField注解,一些特殊情况除外:
-
成员变量名与数据库字段名不一致
-
成员变量是以
isXXX命名,并且类型为Boolean,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。 -
成员变量名与数据库一致,但是与数据库的关键字冲突。使用
@TableField注解给字段名添加转义字符:````````
常见其他属性:


mp的yaml配置:
MybatisPlus也支持基于yaml文件的自定义配置,官方文档:使用配置 | MyBatis-Plus
大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如:
-
实体类的别名扫描包
-
全局id类型
java
mybatis-plus:
type-aliases-package: com.dj.mp.domain.po
global-config:
db-config:
id-type: auto # 全局id类型为自增长
MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置:
java
mybatis-plus:
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。
核心功能:
条件构造器Wrapper--动态 SQL 的 "拼接神器"
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以
id作为where条件以外,还支持更加复杂的where条件。

Wrapper有很多默认实现

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:

QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

实际上我们只需要重点关注常见的三个

Wrapper 核心用法(LambdaQueryWrapper 为主,推荐)
先明确核心原则:链式调用,条件方法自动拼接 AND/OR,无需手动处理逻辑运算符。
- 基础条件方法
1
- 逻辑运算符(AND/OR)
默认多个条件用 AND 连接,如需 OR 可手动调用 .or() 方法:
java
// 示例:查询(年龄>=18 且 用户名含"张")OR(邮箱不为空)
LambdaQueryWrapper new LambdaQueryWrapper
.ge(User::getAge, 18)
.like(User::getUserName, "张")
.or() // 手动切换为OR
.isNotNull(User::getEmail);
// 对应SQL:WHERE age >= ? AND user_name LIKE ? OR email IS NOT NULL
- 非空判断(自动过滤 NULL 值)
Wrapper 会自动忽略值为 null 的条件,无需手动写 test="xxx != null">:
java
Integer minAge = null; // 前端未传该参数
String userName = "张";
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper .ge(minAge != null, User::getAge, minAge) // 若minAge为null,该条件不拼接
.like(User::getUserName, userName);
// 最终SQL:WHERE user_name LIKE '%张%'(age条件被忽略)
第一个参数可传布尔值,控制条件是否生效(如 .ge(Boolean.TRUE, ...) 强制生效)。
不同 Wrapper 实战示例
- LambdaQueryWrapper(查询场景,推荐)
// 需求:查询年龄18-30岁、用户名含"张"、邮箱不为空的用户,按创建时间降序
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper .between(User::getAge, 18, 30)
.like(User::getUserName, "张")
.isNotNull(User::getEmail)
.orderByDesc(User::getCreateTime);
List = userMapper.selectList(wrapper);
// 对应SQL:
// SELECT id,user_name,age,email,create_time,update_time,is_deleted
// FROM user
// WHERE age BETWEEN ? AND ?
// AND user_name LIKE ?
// AND email IS NOT NULL
// ORDER BY create_time DESC
2. QueryWrapper(非 Lambda,简单场景)
// 注意:字段名需手动写字符串,容易拼错(不推荐复杂场景)
QueryWrapper = new QueryWrapper
.eq("age", 25)
.like("user_name", "李")
.orderByAsc("create_time");
ListList = userMapper.selectList(wrapper);
3. UpdateWrapper(动态更新场景)
// 需求:更新用户(ID=1)的用户名和年龄,仅更新非空字段
UpdateWrapper = new UpdateWrapper
.set("user_name", "张三更新") // 强制更新该字段
.set("age", 26)
.eq("id", 1); // WHERE条件
// 或用LambdaUpdateWrapper(避免字段名字符串):
LambdaUpdateWrapper new LambdaUpdateWrapper
.set(User::getUserName, "张三更新")
.set(User::getAge, 26)
.eq(User::getId, 1);
userMapper.update(null, wrapper); // 第一个参数传null,用Wrapper的set条件
// 对应SQL:UPDATE user SET user_name = ?, age = ? WHERE id = ?
多表关联:
理论上来讲MyBatisPlus是不支持多表查询的,不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。
例如,我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL,大概是这样的:
XML
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>
可以看出其中最复杂的就是WHERE条件的编写,如果业务复杂一些,这里的SQL会更变态。
但是基于自定义SQL结合Wrapper的玩法,我们就可以利用Wrapper来构建查询条件,然后手写SELECT及FROM部分,实现多表查询。
查询条件这样来构建:
XML
@Test
void testCustomJoinWrapper() {
// 1.准备自定义查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
// 2.调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
users.forEach(System.out::println);
}
然后在UserMapper中自定义方法:
XML
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
注意:通过${ew.customSqlSegment}来拼接我们wrapper的查询条件片段,其中@Param("ew")中ew是固定的约定写法,不能替换成别的词,如@Param("wrapper"),这样会报错
IService ------ Service 层的 "效率神器"
IService 是 MyBatis-Plus 提供的 Service 层接口,基于 BaseMapper 封装了更全面的 CRUD 方法,同时支持批量操作、链式调用、条件查询(结合 Wrapper)等高级特性。
它解决了传统 Service 层的 2 大痛点:
- 无需手动编写 Service 接口和实现类的重复方法(如 getById、list、saveBatch);
- 内置批量操作、分页查询等常用功能,不用自己封装工具类。
一:IService 核心价值:
- 接口 + 实现类一键生成,减少模板代码;
- 方法命名更贴合业务(如 save 替代 insert、remove 替代 delete);
- 支持 Lambda 条件查询,与 Wrapper 无缝衔接;
- 批量操作默认优化性能(如分批插入)。
二、快速搭建 IService(3 步搞定)
以之前的 User 实体为例,基于 Spring Boot + MP 环境,3 步搭建完整 Service 层:
1创建 UserService 接口,继承 MP 提供的 `IService 添加方法:
XML
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.entity.User;
// 泛型指定实体类User
public interface UserService extends IService> {
// 无需手动添加CRUD方法,IService已内置全套
// 如需自定义业务方法,可在此添加(如 getUserByUserName)
}
2. 实现 Service 类(继承 ServiceImpl)
创建 UserServiceImpl 实现类,继承 ServiceImpl>,并实现自定义接口:
XML
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.entity.User;
import com.example.mp.mapper.UserMapper;
import com.example.mp.service.UserService;
import org.springframework.stereotype.Service;
// 泛型参数1:对应的Mapper接口;参数2:实体类
@Service // 注入Spring容器
public class UserServiceImpl extends ServiceImpl, User> implements UserService {
// 无需重写CRUD方法,ServiceImpl已实现所有IService接口方法
// 如需自定义业务逻辑,在此实现(如重写或新增方法)
}
3. 注入使用(Controller / 其他 Service)
直接在 Controller 中注入 UserService,即可调用内置方法:
XML
import com.example.mp.entity.User;
import com.example.mp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService; // 注入IService实现类
// 直接调用内置方法,无需手动写Service逻辑
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getById(id); // IService内置方法
}
}
三、IService 核心内置方法(常用分类)
IService 封装了近 50 个常用方法,覆盖查询、新增、更新、删除、批量操作、分页等场景,以下是高频用法分类:
1. 单条数据操作
|--------------------------|-------------------|---------------------------------------------------|
| 方法名 | 作用 | 示例代码 |
| getById(Long id) | 根据 ID 查询单条数据 | userService.getById(1L) |
| save(User entity) | 新增单条数据 | userService.save(new User("张三", 25)) |
| updateById(User entity) | 根据 ID 更新 | userService.updateById(new User(1L, "张三更新", 26)) |
| removeById(Long id) | 根据 ID 删除(支持逻辑删除) | userService.removeById(1L) |
2. 批量操作
|-------------------------------|------------------------|-----------------------------------------------------|
| 方法名 | 作用 | 示例代码 |
| saveBatch(List) | 批量新增(默认分批插入,优化性能) | userService.saveBatch(Arrays.asList(user1, user2)) |
| saveOrUpdateBatch(List list) | 批量新增或更新(存在则更新,不存在则新增) | userService.saveOrUpdateBatch(list) |
| updateBatchById(List> list) | 批量更新(根据 ID) | userService.updateBatchById(list) |
| removeByIds(Collection ids) | 批量删除(根据 ID 集合) | userService.removeByIds(Arrays.asList(1L, 2L)) |
对于批量新增,MySQL的客户端连接参数中有这样的一个参数:
rewriteBatchedStatements,可以重写批处理的statement语句默认是关闭,我们可以将其打开,能够提升批处理速度原因:会启用 MySQL Connector 驱动对批量语句的重写功能。具体来说:
它会将多条 SQL 语句合并成一条语句发送到数据库。
例如,原本需要执行多次的 INSERT 语句会被合并为一条批量插入语句。
如何使用? :修改项目中的application.yml文件,在jdbc的url后面添加参数
&rewriteBatchedStatements=true:
javaspring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: MySQL123
- 条件查询(结合 Wrapper,核心)
与 Wrapper 无缝衔接,支持 Lambda 条件构造,无需手动调用 Mapper:
javaimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 示例1:条件查询列表 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<User>() .ge(User::getAge, 18) .like(User::getUserName, "张"); List> userList = userService.list(wrapper); // 直接用Service查询 // 示例2:条件查询单条数据 User user = userService.getOne(wrapper); // 对应SELECT ONE // 示例3:条件删除 boolean success = userService.remove(wrapper); // 对应DELETE WHERE
- 分页查询(内置分页支持)
结合 MP 分页插件,一行代码实现分页,无需手动处理 Page 对象:
java
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 需求:分页查询年龄>=18的用户,第2页,每页10条
Page page = new Page, 10); // 页码(从1开始)、每页条数
LambdaQueryWrapper Wrappers.lambdaQuery(User.class)
.ge(User::getAge, 18);
IPage userService.page(page, wrapper); // 内置分页方法
// 分页结果:userPage.getRecords()(数据列表)、userPage.getTotal()(总条数)、userPage.getPages()(总页数)
- 其他实用方法
|------------------------------------|-------------|-----------------------------------------------------------|
| 方法名 | 作用 | 示例代码 |
| count() | 查询总记录数 | long total = userService.count(); |
| count(LambdaQueryWrapper wrapper) | 条件查询记录数 | long count = userService.count(wrapper); |
| list() | 查询所有数据 | List = userService.list(); |
| chain() | 链式调用(高级用法) | userService.chain().like(User::getUserName, "张").list(); |
四、自定义业务方法(扩展 IService)
如果内置方法满足不了业务需求,可在接口中新增自定义方法,在实现类中实现:
- 自定义接口方法
java
public interface UserService extends IService // 自定义方法:根据用户名查询用户(模糊匹配)
List getUserByUserNameLike(String userName);
}
- 实现类中实现(可调用 baseMapper 或 Wrapper)
java
@Service
public class UserServiceImpl extends ServiceImpl User> implements UserService {
@Override
public ListUserNameLike(String userName) {
// 方式1:调用 baseMapper(ServiceImpl内置属性,无需注入)
return baseMapper.selectList(Wrappers.lambdaQuery(User.class)
.like(User::getUserName, userName));
// 方式2:直接用 Service 内置方法 + Wrapper(推荐)
// return this.list(Wrappers.lambdaQuery(User.class).like(User::getUserName, userName));
}
}
- 调用自定义方法
java
@GetMapping("/like")
public ListLike(@RequestParam String userName) {
return userService.getUserByUserNameLike(userName); // 调用自定义方法
}
扩展功能:
1代码生成插件mybatis-plus
1 分钟生成单表全套代码(Entity+Mapper+IService+ServiceImpl+Controller);
安装插件:

使用:先连接数据库


生成代码:


最终基础代码会自动生成
2静态工具Db:
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:

3枚举处理器和Json处理器
数据库中常有 "状态类字段"(如 user_status:0 - 正常、1 - 禁用、2 - 冻结),传统做法是用 Integer 存储,代码中用常量判断(如 if (userStatus == 1)),存在 2 个痛点:
- 可读性差:数字无法直观表达含义;
- 易出错:硬编码数字可能写错(如把 2 写成 3)。
MP 枚举处理器的价值:让数据库字段与 Java 枚举直接映射,无需手动转换,代码可读性和安全性大幅提升。
首先定义枚举:
java
package com.itheima.mp.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FREEZE(2, "冻结")
;
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
然后把User类中的status字段的Inteager类型改为UserStatus 类型:

要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值 。 MybatisPlus提供了@EnumValue注解来标记枚举属性:

并且,在UserStatus枚举中通过@JsonValue注解标记JSON序列化时展示的字段:

最后配上枚举处理器
XML
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
Json处理器
数据库中常有 "复杂结构字段"(如 ext_info 存储用户额外信息:{"address":"北京","hobby":["篮球","读书"]}),传统做法是用 varchar 存储 JSON 字符串,存在 2 个痛点:
- 代码冗余:需手动将 Java 对象序列化为 JSON 字符串,查询后再反序列化为对象;
- 易出错:手动序列化可能导致 JSON 格式错误,查询时解析复杂。
MP JSON 处理器的价值:让 Java 对象与数据库 JSON 字段直接映射,自动完成序列化 / 反序列化,无需手动处理。
JSON 处理器使用步骤(3 步搞定)
步骤 1:定义 JSON 对应的 Java 实体类
创建存储 JSON 结构的实体类(如用户额外信息):
java
package com.itheima.mp.domain.po;
import lombok.Data;
@Data
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
2接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:

同时,在User类上添加一个注解,声明自动映射:

分页插件
在未引入分页插件的情况下,
MybatisPlus是不支持分页功能的,IService和BaseMapper中的分页方法都无法正常起效。 所以,我们必须配置分页插件。
配置分页插件
在项目中新建一个配置类:

java
package com.itheima.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 MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试:
java
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 2));
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}
