MyBatis-Plus 从入门到实战:高效简化数据库开发的完整指南

文章目录


一、MyBatis-Plus 基础概述

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,由国人开发维护,在 MyBatis 的基础之上只做增强、不做改变。它的核心定位非常清晰:

只为简化开发而生,不对 MyBatis 原有能力做任何侵入式改动。

MP 的核心目标可以归纳为三点:

  1. 简化单表 CRUD :传统 MyBatis 中,即使是最简单的单表查询也需要手写 XML 映射文件或注解 SQL,而 MP 内置的 BaseMapper 直接提供了数十种常用方法,绝大多数单表操作可以做到零 SQL 编写。
  2. 提升开发效率:通过条件构造器(Wrapper)体系,用纯 Java 代码链式拼接 WHERE 条件,避免在 XML 中反复编写重复的条件判断逻辑。
  3. 完全兼容 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 内置了哪些方法? 包括但不限于:insertdeleteByIddeleteByMapupdateByIdselectByIdselectBatchIdsselectListselectPage 等。覆盖了单表增删改查的绝大多数场景。

第五步:编写测试类验证
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%'

代码思路解析:

  1. select("id", "user_name", ...):指定查询返回的字段,不调用则默认 SELECT *
  2. eq("delete_flag", 18):拼接 WHERE delete_flag = 18eq = equals。
  3. like("user_name", "min"):拼接 AND user_name LIKE '%min%',条件间默认用 AND 连接。
  4. 所有方法都返回 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 的优势

QueryWrapperUpdateWrapper 虽然功能强大,但有一个明显的痛点:字段名以硬编码字符串形式书写,容易拼写错误,且项目重构属性名时,这些字符串无法被 IDE 自动关联修改

LambdaQueryWrapperLambdaUpdateWrapper 正是为解决这个问题而生------它们通过实体类方法引用(类名::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 写法的三大优势:

  1. 编译期校验:方法引用如果不合法,编译阶段就会报错,杜绝字段名拼写问题。
  2. 重构友好:实体类属性名修改后,IDE 会自动同步所有方法引用,不会遗漏。
  3. 代码可读性更高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

代码思路解析:

  1. @Param(Constants.WRAPPER) 是固定的写法,Constants.WRAPPER 的值就是 "ew"。也可以直接写 @Param("ew"),但使用常量更规范。
  2. ${ew.customSqlSegment} 中的 $ 不是 #,这意味着它直接拼接 SQL 字符串而非使用预编译占位符。实际 WHERE 内部的参数值仍然是通过 # 预编译安全的------这里 ${} 拼接的是整个 WHERE 子句框架。
  3. 这种设计让你既享受 Wrapper 的动态条件构造能力,又保留了自定义 SQL 的灵活性,是 MP 中最实用的高级特性之一。

全文总结

本文从 MP 的核心定位出发,通过一个完整的示例项目,系统讲解了 MyBatis-Plus 的四大核心模块:

  1. 基础概述:MP 是 MyBatis 的增强工具,只做增强不做改变,核心目标是简化单表 CRUD 开发,同时完全兼容 MyBatis 原生能力。
  2. 五步快速上手:引入依赖 → 配置数据源 → 配置 Mapper 扫描 → 继承 BaseMapper → 直接调用内置方法。全程零 SQL 编写即可完成单表增删改查。
  3. 注解映射机制 :通过 @TableName@TableId@TableField 三大注解灵活控制实体类与数据库表的映射关系,兼顾约定优于配置和精细化控制。
  4. Wrapper 条件构造器QueryWrapper / UpdateWrapper 提供了链式拼接条件的能力,Lambda 系列 Wrapper 进一步消除了字符串硬编码带来的维护隐患。
  5. 自定义 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}

常见问题 / 避坑指南

  1. 表名或字段名不匹配导致 SQL 报错

现象 :执行查询时报 Table 'xxx' doesn't existUnknown column 'xxx'

原因:MP 按驼峰转下划线规则推断表名/字段名,与数据库实际命名不一致。

解决 :使用 @TableName("实际表名")@TableField("实际字段名") 显式指定映射关系。

  1. QueryWrapper 中写错字段名

现象:SQL 执行正常但查询结果不符合预期,或直接报字段不存在。

原因QueryWrappereq()like() 等方法参数是数据库字段名 (如 delete_flag),不是实体类的属性名(如 deleteFlag)。

解决

  • 优先使用 LambdaQueryWrapper,通过方法引用书写,从源头杜绝此类错误。
  • 如果必须用 QueryWrapper,确保传入的是数据库字段名而非 Java 属性名。
  1. 设置了 map-underscore-to-camel-case 但结果映射失败

现象:SQL 执行成功、控制台显示有数据返回,但 Java 对象的属性全是 null。

原因:可能是在 XML 自定义 SQL 中 SELECT 的列别名使用了驼峰命名,与 MP 的自动映射机制产生了冲突。

解决 :统一命名风格------如果开启了驼峰自动映射,数据库查询返回的列名使用下划线格式(如 user_name),让 MP 自动转换为 userName

  1. 自定义 SQL 中 ${ew.customSqlSegment} 不生效

现象:调用自定义方法时,Wrapper 中的条件没有被拼接到 SQL 中。

排查步骤

  • 确认 Mapper 方法参数中使用了 @Param(Constants.WRAPPER)@Param("ew"),这是硬性要求。
  • 确认 SQL 中写的是 ${ew.customSqlSegment}$ 符号),而不是 #{ew.customSqlSegment}# 符号)。# 是预编译占位符,会把整个 WHERE 片段当作字符串参数处理,导致拼接失败。
  • 确认 MP 版本 ≥ 3.0.7,ew 参数名是在这个版本之后才固定下来的。
  1. @TableField(exist = false) 字段在自定义 SQL 中使用

现象 :标记了 exist = false 的属性,在某些场景下能正常赋值,某些场景下不行。

说明exist = false 告诉 MP "这个字段在数据库表中不存在",因此 MP 在自动生成 INSERT / UPDATE SQL 时会忽略该字段。但如果你在自定义 SQL(XML 或 @Select)中手动 SELECT 了这个字段,并且查询结果中包含该列,MyBatis 仍然会将值映射到该属性上。这是 MyBatis 底层行为,不是 MP 的问题------关键在于你自定义 SQL 中 SELECT 了什么列。

  1. 依赖版本与 Spring Boot 版本不匹配

现象 :项目启动时报 ClassNotFoundExceptionNoSuchMethodError 等异常。

解决

  • 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 示例项目编写,读者可参照上述步骤搭建自己的项目进行练习。

相关推荐
CodeBlog-star1 小时前
AI Agent 高并发实战:Redis 限流、队列、缓存与高可用全栈方案
数据库·redis·缓存
TDengine (老段)1 小时前
TDengine taosX 与 Explorer — 数据集成与可视化管理
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
Leon-Ning Liu1 小时前
Oracle 26ai新特性:Lock-Free Reservations(无锁预留)
数据库·oracle
xiaopai9451 小时前
WPS能否替代工程项目管理系统
大数据·数据库·项目管理系统·建米软件
wwwzhouzy1 小时前
SpringBoot 响应式编程
java·spring boot·后端·响应式编程
数据库小学妹1 小时前
空间数据库查询慢怎么排查?索引失效、表膨胀、SQL优化实战(附排查命令)
数据库·性能优化·信创·故障排查·索引调优·空间数据库
冰夏之夜影2 小时前
【解决方案】SpringBoot项目添加ssl证书后不生效问题
spring boot·后端·ssl
Cloud云卷云舒2 小时前
从墨天轮排名中,分析近期增速快、中长期具备高潜力国产数据库
数据库·haishandb·工业云
2601_963870212 小时前
【计算机毕业设计】基于Spring boot+Vue系统的健身俱乐部管理系统的设计与实现
spring boot·后端·课程设计