一. MyBatis-Plus 介绍
MyBatis-Plus(简称 MP)是⼀个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发.提高效率而生 。
特性:
• 润物无声:只做增强不做改变,引入它不会对现有⼯程产生影响,如丝般顺滑.
• 效率至上:只需简单配置,即可快速进行单表CRUD操作,从而节省大量时间.
• 丰富功能:代码生成、自动分页、逻辑删除、自动填充、拦截器等功能⼀应俱全.
• 广泛认可:连续5年获得开源中国年度最佳开源项目殊荣,Github累计16KStar.

支持数据库: PostgreSQL , MySQL , MariaDB , Oracle , SQLServer , OceanBase , H2 , DB2...(任何能使用MyBatis进行增删改查,并且支持标准SQL的数据库应该都在MyBatis-Plus的支持范围内)
更多介绍,可查看 MyBatis-Plus 官网:https://baomidou.com/
二. MyBatis-Plus 使用
2.1 准备工作
数据库数据准备:
sql
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
添加MyBatis-Plus依赖与MySQL数据库依赖:
引入 MyBatis-Plus Starter 依赖:
Spring Boot2
XML
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.15</version>
</dependency>
Spring Boot3
XML
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.15</version>
</dependency>
Spring Boot4 (自3.5.13开始)
XML
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>3.5.15</version>
</dependency>
注意:不同的Spring版本引入不同的MyBatis-Plus依赖得版本不同(根据Spring版本添加相应版本得依赖)
引入MySQL数据库依赖:
XML
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
配置数据库
application.yml文件,配置内容如下:
python
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 控制台打印 SQL日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.2 编码
为数据库中表创建实体类:
java
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
编写Mapper接口类:
java
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
我们需要我们创建的这个 Mapper接口类 去继承 BaseMapper 这个接口
BaseMapper 这个接口是个泛型接口,填的是我们方法返回内容的对象的类型
BaseMapper中定义的方法:

MybatisPlus提供了⼀个基础的 BaseMapper 接口,已经实现了单表的CRUD, 我们自定义的 Mapper只需要继承这个BaseMapper, 就无需自己实现单表CRUD了
2.3 单元测试
java
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
UserInfoMapper userInfoMapper;
@Test
void testInsert() {
UserInfo user = new UserInfo();
user.setUserName("xiaoming");
user.setPassword("123456");
user.setAge(11);
user.setGender(0);
user.setPhone("18610001234");
userInfoMapper.insert(user);
}
@Test
void testSelectById() {
UserInfo user = userInfoMapper.selectById(1L);
System.out.println("user: " + user);
}
@Test
void testSelectByIds() {
List<UserInfo> users = userInfoMapper.selectByIds(List.of(1, 2, 3, 4));
users.forEach(System.out::println);
}
@Test
void testUpdateById() {
UserInfo user = new UserInfo();
user.setId(1);
user.setPassword("4444444");
userInfoMapper.updateById(user);
}
@Test
void testDelete() {
userInfoMapper.deleteById(5);
}
}
执行后的测试用例后的数据库:
三. MyBatis-Plus复杂操作
3.1 常见注解
3.1.1 @TableName
我们来看下面这个日志

发现:在我们并没有告诉要操作表的信息时,MyBatis-Plus就已经知道了我们要操作的是哪一张表,这是为什么----原因:MyBatis-Plus会通过 我们在 BaseMapper<>中传入的实体类来推断对应数据库中的表名(推断列子与规律如下表所示)
| 实体类名 | 表名 | 转换逻辑 |
|---|---|---|
UserInfo |
user_info |
驼峰拆分:User + Info → 转小写 + 下划线连接 → user_info |
Userinfo |
userinfo |
无驼峰分隔 → 直接全小写 → userinfo |
BookInfo |
book_info |
驼峰拆分:Book + Info → 转小写 + 下划线连接 → book_info |
如果我们设定的实体类经上述规律推断出来的表名与数据库中的表名对应不上时,就会报错,这时我们就要使用 @TableName 注解来解决问题
代码如下:(@TableName中填写的是实体类对应的数据库中的表名)
java
@Data
@TableName("user_info")//指定对应的数据库中的表名
public class UserInfo {
@TableId(value="id",type=IdType.AUTO)
private Integer userid;
private String userName;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
3.1.2 @TableField
在我们不添加 @TableField注解前,MyBatis-Plus对于字段与属性的映射关系,也和表名一样,是通过自动判断转发来的,当转化的名称与数据库中属性名对应不上式,我们就需要添@TableField注解,来对应映射关系。如下面代码所示:
java
@Data
@TableName("user_info")//指定对应的数据库中的表名
public class UserInfo {
@TableId(value="id",type=IdType.AUTO)
private Integer userid;
private String userName;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")//只举这一个例子
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
3.1.3 @TableId
注意:
- 在没有添加 @TableId注解之前,MyBatis-Plus默认 id 为主键
- 一个实体表类中只能有一个主键
在执行 testInsert() (该方法没有传入主键id的值)后,我们发现我们增加的这条数据的主键并没有自增,而是一个随机生成的数字 ----- 原因:由于 MyBatis-Plus 默认 id为主键,且针对主键的默认赋值策略是 IdType.ASSIGN_ID(雪花算法)------ 该策略会由 MP 自动生成 19 位分布式唯一的随机长数字,而非依赖数据库自增,因此最终插入的主键是随机数,而非数据库自增的连续值。
如何解决:添加@TableId注解并且修改主键指定自增类型的主键策略
@TableId注解 源码:
java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
String value() default "";
IdType type() default IdType.NONE;
}
value:主键的名称,如果实体类中主键的名称与数据库中主键的名称不一致是,需通过value(其中填写对应数据库中主键的名称);一致时可不填写
type:指定自增类型的主键策略(默认为IdType.NONE)
| IdType 取值 | 含义 | 适用场景 |
|---|---|---|
AUTO |
数据库自增生成主键 | 单体项目、表主键是自增字段(MySQL 的 AUTO_INCREMENT) |
ASSIGN_ID |
MP 用雪花算法生成 19 位长数字 | 分布式项目、需要全局唯一 ID |
INPUT |
手动输入主键值(MP 不生成) | 主键是业务编号(如订单号)、需手动赋值的场景 |
NONE |
无指定(默认兜底为 ASSIGN_ID) | 不推荐直接用,需显式指定 AUTO/ASSIGN_ID |
将 主键 id 修改为自增:代码如下
java
@Data
public class UserInfo {
@TableId(value="id",type=IdType.AUTO)
private Integer userid;
private String userName;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
注意:由于我们在前面生成了一个主键为随机数的数据,因此我们需要修改该表自动递增的起始数

再执行一下测试用例:
java
@Test
void testInsert() {
UserInfo user = new UserInfo();
user.setUserName("huahua");
user.setPassword("666666");
user.setAge(11);
user.setGender(0);
user.setPhone("18610001234");
userInfoMapper.insert(user);
}
执行后数据库----id开始自增

3.2 条件构造器
入门程序里的使用, 都是简单的CRUD, 但是在实际的应用场景中,我们还需要使用更复杂的操作, MyBatisPlus 也给我们提供了相应的支持.
MyBatis-Plus 提供了⼀套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件.Wrapper类 允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句, 从而提高开发效率并减少 SQL 注入的风险.
以下是主要的 Wrapper 类及其功能:
| 类名 | 核心定位 | 核心特性 / 拓展功能 |
|---|---|---|
| AbstractWrapper | 抽象基类(所有 Wrapper 父类) | 提供所有 Wrapper 类共有的方法和属性(如条件拼接、排序、分页等基础能力) |
| QueryWrapper | 通用查询条件构造器 | 继承 AbstractWrapper,额外拓展 select() 方法,支持手动指定查询字段(如只查 id、username) |
| UpdateWrapper | 通用更新条件构造器 | 继承 AbstractWrapper,专注于 UPDATE 操作,支持拼接更新条件 + 手动指定 SET 字段值 |
| LambdaQueryWrapper | Lambda 版查询构造器 | 基于 QueryWrapper,通过 Lambda 表达式引用实体类属性(如 User::getAge),替代硬编码字段名 |
| LambdaUpdateWrapper | Lambda 版更新构造器 | 基于 UpdateWrapper,通过 Lambda 表达式指定更新字段和条件,替代硬编码字段名 |
具体可查看官网:https://baomidou.com/guides/wrapper/
接下来我们来看具体如何实现:
3.2.1 QueryWrapper
QueryWrapper并不只用于查询语句,无论是修改, 删除, 查询,都可以使用 QueryWrapper来构建查询条件.
QueryWrapper 最核心的两大能力:指定查询字段(select) + 构建查询条件(where)
完成以下SQL查询:(查询)
sql
SELECT id,username,password,age FROM user_info WHERE age = 18 AND username
"%min%"
测试代码如下:
java
@Test
void testQueryWrapper(){
QueryWrapper<UserInfo> queryWrapper =new QueryWrapper<UserInfo>()
.select("user_name","password","age")
.eq("age",18)
.like("user_name","min");//链式调用
List<UserInfo> userInfoList=userInfoMapper.selectList(queryWrapper);
}

完成下述SQL查询:(更新)
sql
UPDATE user_info SET delete_flag=? WHERE age < 20
测试代码如下:
java
@Test
void testUpdateByQueryWrapper(){
QueryWrapper<UserInfo> queryWrapper =new QueryWrapper<UserInfo>()
.lt("age",18); //构造条件 where age<18
UserInfo userInfo=new UserInfo();
userInfo.setDeleteFlag(1);
userInfoMapper.update(userInfo,queryWrapper);
}

MyBatis-Plus 条件构造器核心比较方法速查表:(注意:所有基于 AbstractWrapper 派生的条件构造器都可以使用)
| 方法名 | 中文含义 | 对应 SQL 运算符 |
|---|---|---|
lt |
小于 | < |
le |
小于等于 | <= |
ge |
大于等于 | >= |
gt |
大于 | > |
eq |
等于 | = |
ne |
不等于 | <>/!= |
完成下述SQL查询:(删除)
sql
DELETE FROM user_info WHERE age = 18
测试代码:
java
@Test
void testDeleteByQueryWrapper(){
QueryWrapper<UserInfo> queryWrapper =new QueryWrapper<UserInfo>()
.eq("age",18);
userInfoMapper.delete(queryWrapper);
}
3.3.2 UpdateWrapper
对于更新, 我们也可以直接使用 UpdateWrapper, 在不创建实体对象的情况下, 直接设置更新字段和条件.
UpdateWrapper 最核心的两大能力:构建字段赋值(set) + 构建更新条件条件(where)
完成下述SQL查询:(基础更新):
sql
UPDATE user_info SET delete_flag=0, age=5 WHERE id IN (1,2,3)
测试代码:
java
@Test
void testUpdateByUpdateWrapper(){
UpdateWrapper<UserInfo> updateWrapper=new UpdateWrapper<UserInfo>()
.set("delete_flag",0)
.set("age", 5)
.in("id",List.of(1,2,3));
userInfoMapper.update(updateWrapper);
}
完成下述SQL查询:(基于SQL更新)
sql
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
测试代码:
java
@Test
void testUpdateBySQLUpdateWrapper(){
UpdateWrapper<UserInfo> updateWrapper=new UpdateWrapper<UserInfo>()
.setSql("age=age+10")
.in("id",List.of(1,2,3));
userInfoMapper.update(updateWrapper);
}
| 对比项 | set() 方法 |
setSql() 方法 |
|---|---|---|
| 赋值方式 | 静态值赋值(字段 = 固定值) | 自定义 SQL 片段赋值 |
| 适用场景 | 简单赋值(如 status = 1) |
复杂赋值(如字段自增、函数计算) |
| 语法 | set("字段名", 固定值) |
setSql("SQL片段") |
3.3.3 LambdaQueryWrapper
QueryWrapper 和 UpdateWrapper存在⼀个问题,就是需要写死数据库列名字段名 , 如果字段名发生变更,可能会因为测试不到位酿成事故. MyBatis-Plus给我们提供了⼀种基于Lambda表达式的条件构造器,它通过Lambda表达式来引用实体类的属性,从而避免了硬编码字段名,也提高了代码的可读性和可维护性.
接下来我们看下具体使用:
写法一:
java
@Test
void testLambdaQueryWrapper(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>();
queryWrapper.lambda()
.select(UserInfo::getUserName, UserInfo::getPassword,UserInfo::getAge)
.eq(UserInfo::getUserid, 1);
userInfoMapper.selectList(queryWrapper);
}
写法二:
java
@Test
void testLambdaQueryWrapper(){
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper=new LambdaQueryWrapper<UserInfo>()
.select(UserInfo::getUserName, UserInfo::getPassword,UserInfo::getAge)
.eq(UserInfo::getUserid, 1);
userInfoMapper.selectList(lambdaQueryWrapper);
}
3.3.4 LambdaUpdateWrapper
LambdaUpdateWrapper 用法和 LambdaQueryWrapper 相似
java
@Test
void testLambdUpdateByUpdateWrapper(){
//同样有两种写法,这里只写一种
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>();
updateWrapper.lambda()
.set(UserInfo::getDeleteFlag, 0)
.set(UserInfo::getAge, 5)
.in(UserInfo::getUserId, List.of(1,2,3));
userInfoMapper.update(updateWrapper);
}
3.3 自定义SQL
在实际的开发中, MyBatis-Plus提供的操作不能满足我们的实际需求, MyBatis-Plus 也提供了自定义 SQL的功能, 我们可以利用Wrapper构造查询条件,再结合Mapper编写SQL
注意事项:
• 参数命名:在自定义SQL时,传递Wrapper对象作为参数时,参数名必须为ew ,或者使用注解 @Param(Constants.WRAPPER) 明确指定参数为 Wrapper 对象.
• 使用{ew.customSqlSegment} :在SQL语句中,使用 {ew.customSqlSegment} 来 引用Wrapper对象生成的SQL片段.
• 不支持基于entity的where语句:自定义SQL时,Wrapper对象不会基于实体类自动生成 where子句,你需要手动编写完整的SQL语句.(即必须使用占位符)
代码示例1:
sql
select id,username,password,age FROM user_info WHERE username = "admin"
Mapper接口:
java
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
@Select("select id,username,password,age FROM user_info ${ew.customSqlSegment}")
List<UserInfo> queryUserByCustom( @Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);//括号里等同于 Wrapper<UserInfo> ew
}
测试代码:
java
@Test
void queryUserByCustom() {
QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<UserInfo>()
.eq("username","admin");
List<UserInfo> userInfo=userInfoMapper.queryUserByCustom(queryWrapper);
System.out.println(userInfo);
}
代码示例二:
MyBatis-Plus 在 MyBatis 的基础上只做增强不做改变,所以也支持XML的实现方式上述功能也可以使用XML的方式完成
- 配置mapper路径
python
mybatis-plus:
mapper-locations: "classpath*:/mapper/**.xml" # Mapper.xml
- 定义⽅法
java
@Mapper
public interface UserInfoMapper extends BaseMapper {
List queryUserByCustom2(@Param(Constants.WRAPPER) Wrapper wrapper);
}
- 编写XML
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bite.mybatis.plus.mapper.UserInfoMapper">
<select id="queryUserByCustom2">
select id,username,password,age FROM user_info ${ew.customSqlSegment}
</select>
</mapper>
- 测试:
java
@Test
void testQueryUserByCustom2(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>()
.eq("username","admin");
userInfoMapper.queryUserByCustom2(queryWrapper).forEach(System.out::println);
}
代码示例3:
sql
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
Mapper接口:
java
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
@Update("UPDATE user_info SET age = age+ #{addAge} ${ew.customSqlSegment}")
void updateUserByCustom(@Param("addAge") Integer add ,@Param("ew") Wrapper<UserInfo> wrapper);
}
测试代码:
java
@Test
void updateUserByCustom() {
QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<UserInfo>()
.in("id",List.of(1,2,3));
userInfoMapper.updateUserByCustom(10,queryWrapper);
}
