文章目录
-
- [一、MyBatis-Plus 基础概述](#一、MyBatis-Plus 基础概述)
- [二、MyBatis-Plus 快速使用完整步骤](#二、MyBatis-Plus 快速使用完整步骤)
-
- [2.1 基础使用五步流程](#2.1 基础使用五步流程)
-
- [第一步:引入 Maven 依赖](#第一步:引入 Maven 依赖)
- 第二步:配置数据库连接
- [第三步:配置 Mapper 扫描路径](#第三步:配置 Mapper 扫描路径)
- [第四步:编写实体类和 Mapper 接口](#第四步:编写实体类和 Mapper 接口)
- 第五步:编写测试类验证
- [2.2 MP 自动推断规则与实体类注解映射](#2.2 MP 自动推断规则与实体类注解映射)
- [三、Wrapper 条件构造器体系](#三、Wrapper 条件构造器体系)
-
- [3.1 Wrapper 继承结构](#3.1 Wrapper 继承结构)
- [3.2 两大核心 Wrapper 详解](#3.2 两大核心 Wrapper 详解)
- [3.3 Lambda 系列 Wrapper 的优势](#3.3 Lambda 系列 Wrapper 的优势)
- 四、补充重要总结知识点
-
- [4.1 MP 的最大优势](#4.1 MP 的最大优势)
- [4.2 自定义 SQL 兼容规则](#4.2 自定义 SQL 兼容规则)
- 全文总结
- 核心知识点复盘
- [常见问题 / 避坑指南](#常见问题 / 避坑指南)
一、MyBatis-Plus 基础概述
MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,由国人开发维护,在 MyBatis 的基础之上只做增强、不做改变。它的核心定位非常清晰:
只为简化开发而生,不对 MyBatis 原有能力做任何侵入式改动。
MP 的核心目标可以归纳为三点:
- 简化单表 CRUD :传统 MyBatis 中,即使是最简单的单表查询也需要手写 XML 映射文件或注解 SQL,而 MP 内置的
BaseMapper直接提供了数十种常用方法,绝大多数单表操作可以做到零 SQL 编写。 - 提升开发效率:通过条件构造器(Wrapper)体系,用纯 Java 代码链式拼接 WHERE 条件,避免在 XML 中反复编写重复的条件判断逻辑。
- 完全兼容 MyBatis:MP 本质上是对 MyBatis 的一层封装,你在原生 MyBatis 中的所有用法------自定义 SQL、XML 映射、动态 SQL、ResultMap 等------全部保留,随时可以混合使用。
一句话概括:MP 让你用极少的代码完成单表 CRUD,同时完全保留了 MyBatis 的全部能力。
二、MyBatis-Plus 快速使用完整步骤
2.1 基础使用五步流程
下面通过一个完整的示例项目,演示从零搭建 MyBatis-Plus 并完成数据库操作的全过程。
第一步:引入 Maven 依赖
在 Spring Boot 项目的 pom.xml 中添加 MP Starter 依赖:
xml
<!-- MyBatis-Plus Spring Boot Starter(适配 Spring Boot 3.x/4.x) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>3.5.17</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
选型说明 :
mybatis-plus-spring-boot4-starter适配 Spring Boot 3.x 和 4.x 版本。如果你使用的是 Spring Boot 2.x,需要换成mybatis-plus-boot-starter。版本号建议使用最新稳定版,截止本文编写时为 3.5.17。
第二步:配置数据库连接
在 application.yml 中完成 MySQL 连接配置:
yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 扩展配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志输出,方便调试
map-underscore-to-camel-case: true # 开启驼峰命名自动映射
配置要点:
log-impl开启后,每次执行的 SQL 语句和参数都会打印到控制台,强烈建议开发阶段开启,上线时可关闭。map-underscore-to-camel-case设为true后,数据库字段user_name会自动映射到实体类属性userName,无需额外配置。
第三步:配置 Mapper 扫描路径
在启动类上使用 @MapperScan 注解,指定 Mapper 接口所在的包路径:
java
package com.zmt.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.zmt.mybatisplus.mapper") // 扫描 Mapper 接口所在包
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
两种方式标识 Mapper:
- 方式一(推荐) :在启动类上加
@MapperScan("包路径"),一次配置全局生效,无需在每个 Mapper 接口上逐个加注解。 - 方式二 :在每个 Mapper 接口上单独添加
@Mapper注解。适合 Mapper 数量较少的项目,或需要在不同包中分散管理 Mapper 的场景。
第四步:编写实体类和 Mapper 接口
首先创建实体类,对应数据库中的 user_info 表:
java
package com.zmt.mybatisplus.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
@Data
@TableName("user_info") // 显式指定映射的数据表名
public class Userinfo {
@TableId(value = "id", type = IdType.AUTO) // 主键字段,自增策略
private Integer id;
@TableField("user_name") // 属性名与字段名不一致时,手动指定映射
private String userName;
private String password;
@TableField(exist = false) // 标记该属性在数据库表中不存在
private Integer age;
@TableField(exist = false)
private Integer gender;
@TableField(exist = false)
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
然后编写 Mapper 接口,只需继承 BaseMapper<实体类> 即可获得全部内置方法:
java
package com.zmt.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zmt.mybatisplus.model.Userinfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper extends BaseMapper<Userinfo> {
// 无需写任何方法,BaseMapper 已内置所有常用单表 CRUD 方法
}
BaseMapper内置了哪些方法? 包括但不限于:insert、deleteById、deleteByMap、updateById、selectById、selectBatchIds、selectList、selectPage等。覆盖了单表增删改查的绝大多数场景。
第五步:编写测试类验证
java
package com.zmt.mybatisplus.mapper;
import com.zmt.mybatisplus.model.Userinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper; // 注入 Mapper 对象
@Test
public void testSelect() {
System.out.println("----- selectAll method test ------");
// selectList(null) 自动生成 SELECT * FROM user_info
List<Userinfo> userList = userInfoMapper.selectList(null);
userList.forEach(System.out::println);
}
@Test
void testInsert() {
Userinfo userInfo = new Userinfo();
userInfo.setUserName("bit2");
userInfo.setPassword("bit2");
userInfo.setGender(1);
userInfo.setAge(18);
int insert = userInfoMapper.insert(userInfo);
System.out.println("影响行数:" + insert);
}
@Test
void testUpdate() {
Userinfo userInfo = new Userinfo();
userInfo.setId(14);
userInfo.setUserName("bit33333");
userInfo.setPassword("bit33333");
userInfoMapper.updateById(userInfo); // 根据主键更新
}
@Test
void testDelete() {
userInfoMapper.deleteById(14); // 根据主键删除
}
}
代码思路解析:
selectList(null):参数传null表示无条件,MP 会自动拼接SELECT * FROM user_info查询全表数据。insert(userInfo):MP 会根据实体对象中不为 null 的属性自动生成 INSERT 语句。updateById(userInfo):根据主键id进行更新,只更新对象中非 null 的字段。deleteById(14):根据主键值删除对应记录。
至此,一个完整的 MP 项目就搭建完成了。你会发现:整个过程中没有写一行 SQL 语句,就已经完成了单表的增删改查操作。
2.2 MP 自动推断规则与实体类注解映射
MP 的核心优势之一是约定优于配置------它会根据实体类自动推断对应的数据库表信息,同时支持通过注解进行精细化映射。
表名推断规则
MP 默认将实体类名按驼峰命名转换成下划线命名来匹配数据表:
| 实体类名 | 推断表名 | 转换规则 |
|---|---|---|
UserInfo |
user_info |
大写字母前插入下划线,全小写 |
BookInfo |
book_info |
同上 |
OrderDetail |
order_detail |
同上 |
如果表名和推断结果不一致,使用 @TableName("真实表名") 显式指定:
java
@TableName("user_info") // 明确告诉 MP:这个实体类对应 user_info 表
public class Userinfo { ... }
为什么实体类叫
Userinfo而不是UserInfo? 这是有意为之的例子:如果类名是Userinfo(小写的i),MP 按驼峰规则推断出来的表名是userinfo(全小写无下划线),而实际表名是user_info(带下划线),两者不匹配。此时就必须用@TableName显式绑定,否则会报 "表不存在" 的错误。这里也提醒大家:类名尽量遵循规范的驼峰命名,可以减少不必要的注解配置。
主键推断规则
MP 默认将实体类中名为 id 的属性识别为主键。如果你的主键字段不叫 id,或需要指定主键生成策略,使用 @TableId 注解:
java
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
常用主键策略 IdType 说明:
| 策略 | 说明 | 适用场景 |
|---|---|---|
AUTO |
数据库自增 ID | MySQL 自增主键,最常用 |
ASSIGN_ID |
MP 自动分配 Snowflake 雪花算法 ID(Long 型) | 分布式系统,避免 ID 冲突 |
INPUT |
手动输入 ID | 需要自定义 ID 值的场景 |
NONE |
无策略,跟随全局设置 | 默认行为 |
字段推断规则
MP 默认将实体类属性名按驼峰转下划线匹配数据库字段。当属性名和字段名不一致时,使用 @TableField 注解:
java
@TableField("user_name") // 属性名为 userName,数据库字段为 user_name → 手动绑定
private String userName;
@TableField(exist = false) // 这个属性在数据库表中不存在,MP 不对其做字段映射
private Integer age;
@TableField 的常见用法:
| 场景 | 配置 | 说明 |
|---|---|---|
| 字段名不匹配 | @TableField("user_name") |
显式指定映射的数据库字段名 |
| 属性非表字段 | @TableField(exist = false) |
该属性不参与 SQL 拼接,常用于业务辅助字段 |
| 忽略空值更新 | @TableField(updateStrategy = FieldStrategy.IGNORED) |
控制更新策略 |
三、Wrapper 条件构造器体系
当查询不再是"查全部",而是需要加 WHERE 条件时,就需要用到 MP 的核心武器------Wrapper 条件构造器 。它让你用纯 Java 代码链式拼接查询/更新条件,告别在 XML 中手动编写 <where> 和 <if> 标签的繁琐写法。
3.1 Wrapper 继承结构
MP 的条件构造器体系层次分明,所有实现类都继承自抽象父类 AbstractWrapper:
AbstractWrapper(抽象父类,定义通用条件方法)
├── QueryWrapper → 用于构建 SELECT 的 WHERE 条件
├── LambdaQueryWrapper → 基于 Lambda 表达式的查询条件构造器
├── UpdateWrapper → 用于构建 UPDATE 的 SET + WHERE 条件
└── LambdaUpdateWrapper → 基于 Lambda 表达式的更新条件构造器
四大实现类的职责分工:
| 构造器 | 用途 | 特点 |
|---|---|---|
QueryWrapper |
构建查询条件 | 字段名以字符串形式书写 |
LambdaQueryWrapper |
构建查询条件 | 通过实体类方法引用书写字段,编译期校验 |
UpdateWrapper |
构建更新条件 | 支持 set() 设置字段值 + WHERE 条件 |
LambdaUpdateWrapper |
构建更新条件 | Lambda 写法 + Update 能力 |
3.2 两大核心 Wrapper 详解
QueryWrapper:查询条件构造器
QueryWrapper 专门用于拼接 SELECT 语句的 WHERE 条件,支持等于、不等于、大于、小于、模糊匹配等各类判断条件,并且支持链式调用,可自由组合 AND / OR 逻辑。
基础条件查询示例:
java
@Test
void testQueryWrapper() {
// 1. 创建 QueryWrapper 对象
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
// 2. 链式拼接查询条件:选择字段 + WHERE 条件
queryWrapper
.select("id", "user_name", "password", "delete_flag") // 指定返回字段
.eq("delete_flag", 18) // WHERE delete_flag = 18
.like("user_name", "min"); // AND user_name LIKE '%min%'
// 3. 调用 selectList 执行查询
List<Userinfo> userinfos = userInfoMapper.selectList(queryWrapper);
userinfos.forEach(System.out::println);
}
生成的 SQL(已开启日志时可见):
sql
SELECT id, user_name, password, delete_flag
FROM user_info
WHERE delete_flag = 18 AND user_name LIKE '%min%'
代码思路解析:
select("id", "user_name", ...):指定查询返回的字段,不调用则默认SELECT *。eq("delete_flag", 18):拼接WHERE delete_flag = 18,eq= equals。like("user_name", "min"):拼接AND user_name LIKE '%min%',条件间默认用 AND 连接。- 所有方法都返回
this,支持链式调用,代码简洁流畅。
QueryWrapper 常用条件方法速查:
| 方法 | SQL 等价 | 说明 |
|---|---|---|
eq("字段", 值) |
字段 = 值 |
等于 |
ne("字段", 值) |
字段 != 值 |
不等于 |
gt("字段", 值) |
字段 > 值 |
大于 |
lt("字段", 值) |
字段 < 值 |
小于 |
ge("字段", 值) |
字段 >= 值 |
大于等于 |
le("字段", 值) |
字段 <= 值 |
小于等于 |
like("字段", 值) |
字段 LIKE '%值%' |
模糊匹配 |
in("字段", 集合) |
字段 IN (v1, v2, ...) |
范围查询 |
between("字段", v1, v2) |
字段 BETWEEN v1 AND v2 |
区间查询 |
orderByAsc("字段") |
ORDER BY 字段 ASC |
升序排序 |
orderByDesc("字段") |
ORDER BY 字段 DESC |
降序排序 |
注意 :
QueryWrapper中的字段名是数据库字段名 (如delete_flag),而不是实体类的属性名(如deleteFlag)。这是新手最容易踩的坑。
UpdateWrapper:更新条件构造器
UpdateWrapper 用于构造更新条件,和 QueryWrapper 用法类似,同样支持链式调用与多条件逻辑组合。它的核心优势是:无需先创建实体对象,直接通过 set() 方法指定要更新的字段和值。
java
@Test
void testUpdateWrapper() {
// 创建 UpdateWrapper,直接设置更新字段和条件
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper
.lt("delete_flag", 20) // WHERE delete_flag < 20
.set("delete_flag", 1); // SET delete_flag = 1
// 执行更新------无需传入实体对象
userInfoMapper.update(null, updateWrapper);
}
生成的 SQL:
sql
UPDATE user_info SET delete_flag = 1 WHERE delete_flag < 20
与更新实体方式对比:
| 方式 | 代码量 | 灵活性 | 适用场景 |
|---|---|---|---|
updateById(实体) |
少 | 低,只能按主键更新固定字段 | 简单的单条记录全量更新 |
UpdateWrapper + set() |
中 | 高,支持任意条件 + 任意字段组合 | 按条件批量更新指定字段 |
高级场景------SQL 片段拼接:
java
@Test
void testUpdateWrapper2() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper
.setSql("delete_flag = delete_flag + 10") // 直接拼接 SQL 片段
.in("id", List.of(1, 13, 15)); // WHERE id IN (1, 13, 15)
userInfoMapper.update(updateWrapper);
}
生成的 SQL 为 UPDATE user_info SET delete_flag = delete_flag + 10 WHERE id IN (1, 13, 15)。setSql() 方法让你可以在更新中编写原生 SQL 表达式,满足字段自增、运算等特殊需求。
3.3 Lambda 系列 Wrapper 的优势
QueryWrapper 和 UpdateWrapper 虽然功能强大,但有一个明显的痛点:字段名以硬编码字符串形式书写,容易拼写错误,且项目重构属性名时,这些字符串无法被 IDE 自动关联修改。
LambdaQueryWrapper 和 LambdaUpdateWrapper 正是为解决这个问题而生------它们通过实体类方法引用(类名::get属性名)来书写条件,完全消除硬编码字符串。
对比示例:
java
// 传统写法:字段名为字符串,拼写错误在编译期无法发现
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper
.select("id", "user_name", "password") // 字符串,拼错不会报错
.eq("delete_flag", 15); // 同上
// Lambda 写法:通过方法引用,编译期校验,属性名改动能自动同步
queryWrapper.lambda()
.select(Userinfo::getId, Userinfo::getUserName, Userinfo::getPassword)
.eq(Userinfo::getDeleteFlag, 15);
Lambda 写法的三大优势:
- 编译期校验:方法引用如果不合法,编译阶段就会报错,杜绝字段名拼写问题。
- 重构友好:实体类属性名修改后,IDE 会自动同步所有方法引用,不会遗漏。
- 代码可读性更高 :
Userinfo::getDeleteFlag比字符串"delete_flag"语义更清晰。
UpdateWrapper 同样支持 Lambda 写法:
java
@Test
void testLambdaUpdateWrapper() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda()
.set(Userinfo::getDeleteFlag, 0) // SET delete_flag = 0
.set(Userinfo::getDeleteFlag, 5) // SET delete_flag = 5(覆盖上一步)
.in(Userinfo::getId, List.of(1, 13, 15)); // WHERE id IN (1, 13, 15)
userInfoMapper.update(updateWrapper);
}
使用建议:日常开发优先选择 Lambda 系列 Wrapper。只有当需要处理动态字段名(如按用户选择的列排序)的极少数场景时,再降级使用字符串方式的 QueryWrapper / UpdateWrapper。
四、补充重要总结知识点
4.1 MP 的最大优势
回顾整个使用流程,MP 的核心价值可以浓缩为:
对简单 CRUD 操作,几乎零 SQL 代码;对复杂查询,完全保留 MyBatis 原生能力,想怎么写就怎么写。
| 对比维度 | 原生 MyBatis | MyBatis-Plus |
|---|---|---|
| 简单查询 | 需手写 SQL / XML | selectList() 一行搞定 |
| 条件查询 | XML 中写 <if> + <where> |
Wrapper 链式调用 |
| 分页查询 | 手写 LIMIT + COUNT 查询 | Page + selectPage() 内置支持 |
| 主键策略 | 手动处理 | @TableId 一行配置 |
| 复杂联表查询 | XML 手写 SQL | 完全兼容,仍可手写 SQL + XML |
一句话总结:MP 让开发者把精力聚焦在核心业务逻辑和复杂 SQL 上,而不是浪费在重复的 CRUD 模板代码中。
4.2 自定义 SQL 兼容规则
MP 并不强制你只使用内置方法------它完全兼容 MyBatis 的自定义 SQL 写法。当内置方法无法满足需求时,你可以自由编写自定义 SQL,并且可以将 Wrapper 构造好的条件传入自定义 SQL 中复用。
核心规则(重要):
MP 版本 ≥ 3.0.7 时,自定义 Mapper 方法中如果接收
Wrapper对象作为参数,该参数的命名必须为ew(对应 MP 内部常量Constants.WRAPPER)。在自定义 SQL 中通过${ew.customSqlSegment}即可引用 Wrapper 拼接好的 WHERE 条件片段。
实战示例------Mapper 接口中定义自定义方法:
java
@Mapper
public interface UserInfoMapper extends BaseMapper<Userinfo> {
// 纯自定义 SQL(不使用 Wrapper 条件)
@Select("select id, user_name, password from user_info where user_name = #{user_name}")
List<Userinfo> selectListByCustom(String user_name);
// 自定义 SQL + Wrapper 动态条件(注解方式)
@Select("select id, user_name, password from user_info ${ew.customSqlSegment}")
List<Userinfo> selectListByCustom2(@Param(Constants.WRAPPER) Wrapper<Userinfo> wrapper);
// 自定义 SQL + Wrapper 动态条件(XML 方式)
List<Userinfo> selectListByCustom3(@Param(Constants.WRAPPER) Wrapper<Userinfo> wrapper);
// 自定义更新 + Wrapper 动态条件
@Update("update user_info set delete_flag = delete_flag + #{age} ${ew.customSqlSegment}")
Integer updateByCustom(@Param("age") Integer age,
@Param(Constants.WRAPPER) Wrapper<Userinfo> wrapper);
}
对应的 XML 映射文件(mapper/UserInfoMapper.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.zmt.mybatisplus.mapper.UserInfoMapper">
<select id="selectListByCustom3"
resultType="com.zmt.mybatisplus.model.Userinfo">
select id, userName, password, age from user_info ${ew.customSqlSegment}
</select>
</mapper>
测试调用------Wrapper 条件与自定义 SQL 联动:
java
@Test
void testSelectListByCustom2() {
// 用 QueryWrapper 构造条件
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq("user_name", "admin")
.eq("delete_flag", 15);
// 传入自定义方法,${ew.customSqlSegment} 自动替换为 WHERE 子句
List<Userinfo> userinfos = userInfoMapper.selectListByCustom2(queryWrapper);
userinfos.forEach(System.out::println);
}
实际执行的 SQL:
sql
SELECT id, user_name, password FROM user_info WHERE user_name = 'admin' AND delete_flag = 15
代码思路解析:
@Param(Constants.WRAPPER)是固定的写法,Constants.WRAPPER的值就是"ew"。也可以直接写@Param("ew"),但使用常量更规范。${ew.customSqlSegment}中的$不是#,这意味着它直接拼接 SQL 字符串而非使用预编译占位符。实际 WHERE 内部的参数值仍然是通过#预编译安全的------这里${}拼接的是整个 WHERE 子句框架。- 这种设计让你既享受 Wrapper 的动态条件构造能力,又保留了自定义 SQL 的灵活性,是 MP 中最实用的高级特性之一。
全文总结
本文从 MP 的核心定位出发,通过一个完整的示例项目,系统讲解了 MyBatis-Plus 的四大核心模块:
- 基础概述:MP 是 MyBatis 的增强工具,只做增强不做改变,核心目标是简化单表 CRUD 开发,同时完全兼容 MyBatis 原生能力。
- 五步快速上手:引入依赖 → 配置数据源 → 配置 Mapper 扫描 → 继承 BaseMapper → 直接调用内置方法。全程零 SQL 编写即可完成单表增删改查。
- 注解映射机制 :通过
@TableName、@TableId、@TableField三大注解灵活控制实体类与数据库表的映射关系,兼顾约定优于配置和精细化控制。 - Wrapper 条件构造器 :
QueryWrapper/UpdateWrapper提供了链式拼接条件的能力,Lambda 系列 Wrapper 进一步消除了字符串硬编码带来的维护隐患。 - 自定义 SQL 兼容 :通过
${ew.customSqlSegment}将 Wrapper 条件注入自定义 SQL,实现动态条件 + 手写 SQL 的完美结合。
核心知识点复盘
| 知识点 | 要点 |
|---|---|
| MP 定位 | 增强而非替代 MyBatis,完全兼容原生功能 |
| 依赖选型 | Spring Boot 3.x/4.x 用 mybatis-plus-spring-boot4-starter |
| Mapper 扫描 | @MapperScan 一次性配置优于逐接口加 @Mapper |
BaseMapper |
继承即获得全套单表 CRUD 方法,无需编写任何实现 |
| 表名推断 | 实体类名驼峰转下划线,不一致时用 @TableName |
| 主键策略 | IdType.AUTO(自增)和 IdType.ASSIGN_ID(雪花算法)最常用 |
| 字段映射 | @TableField 解决属性名与字段名不匹配、非表字段标记 |
QueryWrapper |
链式拼接 SELECT 的 WHERE 条件,字段名用数据库字段名 |
UpdateWrapper |
无需实体对象,set() + 条件链式构造 UPDATE 语句 |
| Lambda Wrapper | 通过方法引用消除字符串硬编码,编译期安全,重构友好 |
| 自定义 SQL + Wrapper | 参数用 @Param(Constants.WRAPPER),SQL 中用 ${ew.customSqlSegment} |
常见问题 / 避坑指南
- 表名或字段名不匹配导致 SQL 报错
现象 :执行查询时报 Table 'xxx' doesn't exist 或 Unknown column 'xxx'。
原因:MP 按驼峰转下划线规则推断表名/字段名,与数据库实际命名不一致。
解决 :使用 @TableName("实际表名") 和 @TableField("实际字段名") 显式指定映射关系。
- QueryWrapper 中写错字段名
现象:SQL 执行正常但查询结果不符合预期,或直接报字段不存在。
原因 :QueryWrapper 的 eq()、like() 等方法参数是数据库字段名 (如 delete_flag),不是实体类的属性名(如 deleteFlag)。
解决:
- 优先使用
LambdaQueryWrapper,通过方法引用书写,从源头杜绝此类错误。 - 如果必须用 QueryWrapper,确保传入的是数据库字段名而非 Java 属性名。
- 设置了
map-underscore-to-camel-case但结果映射失败
现象:SQL 执行成功、控制台显示有数据返回,但 Java 对象的属性全是 null。
原因:可能是在 XML 自定义 SQL 中 SELECT 的列别名使用了驼峰命名,与 MP 的自动映射机制产生了冲突。
解决 :统一命名风格------如果开启了驼峰自动映射,数据库查询返回的列名使用下划线格式(如 user_name),让 MP 自动转换为 userName。
- 自定义 SQL 中
${ew.customSqlSegment}不生效
现象:调用自定义方法时,Wrapper 中的条件没有被拼接到 SQL 中。
排查步骤:
- 确认 Mapper 方法参数中使用了
@Param(Constants.WRAPPER)或@Param("ew"),这是硬性要求。 - 确认 SQL 中写的是
${ew.customSqlSegment}($符号),而不是#{ew.customSqlSegment}(#符号)。#是预编译占位符,会把整个 WHERE 片段当作字符串参数处理,导致拼接失败。 - 确认 MP 版本 ≥ 3.0.7,
ew参数名是在这个版本之后才固定下来的。
@TableField(exist = false)字段在自定义 SQL 中使用
现象 :标记了 exist = false 的属性,在某些场景下能正常赋值,某些场景下不行。
说明 :exist = false 告诉 MP "这个字段在数据库表中不存在",因此 MP 在自动生成 INSERT / UPDATE SQL 时会忽略该字段。但如果你在自定义 SQL(XML 或 @Select)中手动 SELECT 了这个字段,并且查询结果中包含该列,MyBatis 仍然会将值映射到该属性上。这是 MyBatis 底层行为,不是 MP 的问题------关键在于你自定义 SQL 中 SELECT 了什么列。
- 依赖版本与 Spring Boot 版本不匹配
现象 :项目启动时报 ClassNotFoundException 或 NoSuchMethodError 等异常。
解决:
- Spring Boot 2.x → 使用
mybatis-plus-boot-starter - Spring Boot 3.x / 4.x → 使用
mybatis-plus-spring-boot4-starter - 如果项目使用的是 MyBatis-Plus 3.5.x 以下的老版本,建议升级到最新稳定版,以获取更好的 Spring Boot 高版本兼容性。
本文所有代码均基于实际可运行的 Spring Boot + MyBatis-Plus 示例项目编写,读者可参照上述步骤搭建自己的项目进行练习。