JavaEE进阶——MyBatis-Plus新手完全攻略

目录

[MyBatis-Plus 新手完全攻略:从入门到精通](#MyBatis-Plus 新手完全攻略:从入门到精通)

[1. 什么是 MyBatis-Plus?(核心概念)](#1. 什么是 MyBatis-Plus?(核心概念))

[1.1 背景知识:从 JDBC 到 ORM 的进化之路](#1.1 背景知识:从 JDBC 到 ORM 的进化之路)

[1.2 MP 的出现:懒人的福音与效率的飞跃](#1.2 MP 的出现:懒人的福音与效率的飞跃)

[2. 代码中的核心知识点详解与避坑指南](#2. 代码中的核心知识点详解与避坑指南)

[2.1 实体类 (Entity) 与 Lombok 的最佳实践](#2.1 实体类 (Entity) 与 Lombok 的最佳实践)

[2.2 注解映射 (Mapping Annotations) ------ MP 是如何"看懂"数据库的](#2.2 注解映射 (Mapping Annotations) —— MP 是如何“看懂”数据库的)

[2.3 BaseMapper 的泛型魔法](#2.3 BaseMapper 的泛型魔法)

[3. 条件构造器 (Wrapper) - 代码中的重难点](#3. 条件构造器 (Wrapper) - 代码中的重难点)

[3.1 QueryWrapper (基础版 ------ 并不推荐在生产环境大量使用)](#3.1 QueryWrapper (基础版 —— 并不推荐在生产环境大量使用))

[3.2 LambdaQueryWrapper (进阶推荐版 ------ 必须掌握)](#3.2 LambdaQueryWrapper (进阶推荐版 —— 必须掌握))

[4. 扩展知识点 (面试与实战必知)](#4. 扩展知识点 (面试与实战必知))

[4.1 逻辑删除 (Logical Delete) ------ 数据安全的安全带](#4.1 逻辑删除 (Logical Delete) —— 数据安全的安全带)

[4.2 自动填充 (Auto Fill) ------ 告别重复劳动](#4.2 自动填充 (Auto Fill) —— 告别重复劳动)

[4.3 为什么不要在循环中调用数据库?(N+1 问题)](#4.3 为什么不要在循环中调用数据库?(N+1 问题))

[5. 如何阅读接下来的代码?](#5. 如何阅读接下来的代码?)

[MyBatis-Plus 实战项目运行指南](#MyBatis-Plus 实战项目运行指南)

第一步:环境准备 (必看)

第二步:搭建工程骨架

第三步:复制文件 (核心步骤)

第四步:初始化数据库

第五步:运行测试

[1. 批量修改数据 (UpdateWrapper)](#1. 批量修改数据 (UpdateWrapper))

[2. 数学运算更新 (SQL 片段)](#2. 数学运算更新 (SQL 片段))

[3. 单条更新 (UpdateById)](#3. 单条更新 (UpdateById))

[4. 精准查询 (LambdaQueryWrapper)](#4. 精准查询 (LambdaQueryWrapper))

[5. 批量查询 (SelectByIds)](#5. 批量查询 (SelectByIds))

[6. 条件更新 (QueryWrapper)](#6. 条件更新 (QueryWrapper))

[7. 删除操作 (Delete)](#7. 删除操作 (Delete))

[8. 插入新用户 (Insert)](#8. 插入新用户 (Insert))

[🎓 总结](#🎓 总结)

[方法一:使用 IDEA 的"断点调试" (Debug 模式) ------ 上帝视角 🕵️‍♂️](#方法一:使用 IDEA 的“断点调试” (Debug 模式) —— 上帝视角 🕵️‍♂️)

[方法二:手动"单步执行" ------ 稳扎稳打 🐢](#方法二:手动“单步执行” —— 稳扎稳打 🐢)

[方法三:在代码里"强行睡觉" (不推荐但直观) 🛌](#方法三:在代码里“强行睡觉” (不推荐但直观) 🛌)

[💡 总结建议](#💡 总结建议)


你好!作为新手小白,面对 MyBatis-Plus (MP) 这样强大的框架,感到困惑是很正常的。MyBatis-Plus 其实就像是给 MyBatis 穿上了一套"钢铁侠战衣",让原本繁琐的数据库操作变得极其简单。

为了让你能够彻底理解,我为你准备了一份超详细的沉浸式学习指南。这不仅仅是代码,更是一份涵盖了原理、扩展知识和最佳实践的教程。

我为你生成了 5 个文件:

  1. MyBatisPlus_Guide.md: 核心教程文档。包含对所有知识点的深度解析、扩展说明、背景知识(如 ORM、Lambda 表达式原理)以及"为什么这样写"的解释。请务必配合代码阅读。

  2. UserInfo.java: 实体类代码。包含极其详细的注解和字段说明。

  3. UserInfoMapper.java: Mapper 接口代码。包含自定义 SQL 的定义。

  4. MybatisPlusTests.java: 测试用例代码。这是核心,包含了 PDF 中所有的增删改查、复杂查询、Lambda 构造器等操作,每一行都有详细注释。

  5. schema.sql: 数据库脚本。用于初始化环境。

请先阅读 Guide 文档 ,然后对照 Tests 代码 进行实践。

MyBatis-Plus 超详细学习指南 (新手必读).md

MyBatis-Plus 新手完全攻略:从入门到精通

1. 什么是 MyBatis-Plus?(核心概念)

1.1 背景知识:从 JDBC 到 ORM 的进化之路

在学习 MyBatis-Plus (简称 MP) 之前,你需要先理解为什么我们需要它。这一切都要从 Java 连接数据库的历史说起:

  • JDBC (Java Database Connectivity) ------ 痛苦的起点:

    这是 Java 连接数据库的最底层标准。虽然功能强大,但写起来非常折磨人。

    • 繁琐的样板代码 :为了执行一个简单的 SQL,你需要手动加载驱动、通过 DriverManager 获取连接、创建 Statement、处理 ResultSet 结果集,最后还要在 finally 块中小心翼翼地关闭资源以防内存泄漏。

    • 硬编码的噩梦:SQL 语句是写死在 Java 代码里的字符串。一旦数据库字段改名,你需要在 Java 代码里满世界搜索替换,极易出错。

  • ORM (Object-Relational Mapping) ------ 桥梁:

    为了解决上述痛苦,ORM(对象关系映射)应运而生。它的核心思想是建立表 (Table) 和 类 (Class) 之间的映射关系,让程序员像操作 Java 对象一样操作数据库。

    • 数据库表的一行数据 (Row) <---> Java 类的一个对象 (Object)

    • 数据库表的列 (Column) <---> Java 类的属性 (Field)

MyBatis 是一个优秀的"半自动化" ORM 框架。它成功地把 SQL 从 Java 代码中剥离到了 XML 配置文件中,解决了硬编码问题。但它仍然要求你为每一个查询方法(甚至是最简单的 selectById)编写 SQL 语句,这在表字段很多时依然是一项繁重的工作。

1.2 MP 的出现:懒人的福音与效率的飞跃

MyBatis-Plus 的口号是 "只做增强不做改变",这句话有两个层面的含义:

  • 不改变(兼容性) :它完全兼容 MyBatis。你原本习惯手写的 XML SQL、复杂的关联查询 (ResultMap)、动态 SQL 标签,在引入 MP 后依然可以照常使用。MP 不会破坏你现有的工程结构。

  • 只增强(生产力) :这是 MP 的杀手锏。它内置了通用的 BaseMapper 接口。

    • 以前的痛点 :哪怕只是写一个"根据 ID 查询用户"或"插入一个用户",你都需要先定义 Mapper 接口方法,再去 XML 里写 <select><insert> 标签,如果不小心把列名拼错了,程序跑起来才会报错。

    • 现在的爽点 :你的 Mapper 接口只需要继承 BaseMapper,这就好比让你的接口"继承"了一个拥有标准 CRUD 能力的父类。MP 会在启动时自动帮你注入 insertdeleteByIdupdateByIdselectById 等十几个通用方法的 SQL 实现。对于单表操作,你真的一行 SQL 都不用写!

2. 代码中的核心知识点详解与避坑指南

在阅读生成的 Java 代码前,请先掌握以下核心概念,这对应了代码中的关键部分。

2.1 实体类 (Entity) 与 Lombok 的最佳实践

UserInfo.java 中,你会看到 @Data 注解。

  • 知识点Lombok 是一个在 Java 编译阶段生效的工具库。

  • 深层作用 :它不是在运行时通过反射生成代码,而是利用 Java 的注解处理器(Annotation Processing),在代码编译成 .class 文件时,自动"织入"了 getsettoStringhashCodeequals 等方法的字节码。

  • 新手扩展

    • 为什么要用? 如果没有 Lombok,一个拥有 10 个字段的 UserInfo 类,其 Getter/Setter 代码可能占据 100 多行。这不仅让核心逻辑(字段定义)被淹没,而且当你修改字段类型时(比如把 int 改成 Integer),还需要手动去改下面的配套方法,非常容易漏改。

    • 注意:使用 Lombok 需要在 IDE(如 IDEA)中安装 Lombok 插件,否则 IDE 无法识别这些自动生成的方法,会提示代码报错。

2.2 注解映射 (Mapping Annotations) ------ MP 是如何"看懂"数据库的

MP 如何知道 Java 类对应数据库哪张表?靠的是约定大于配置 的设计理念以及注解辅助。

  1. @TableName("user_info"):

    • 约定 :默认情况下,MP 采用"驼峰转下划线"的规则。如果你的类名是 UserInfo,MP 会默认去数据库找 user_info 表。

    • 配置 :现实中数据库表名千奇百怪。如果表名是 t_sys_user,或者为了兼容旧系统叫 tblUser,这就破坏了约定。此时必须使用 @TableName("t_sys_user") 显式告诉 MP:"别猜了,就是这张表"。

  2. @TableId (主键策略):

    • 核心作用 :告诉 MP 哪个字段是主键 。这至关重要,因为 updateByIddeleteById 等方法生成的 WHERE 子句完全依赖于此。

    • 深度扩展(IdType)

      • IdType.AUTO数据库自增 。依赖数据库本身的 AUTO_INCREMENT 特性。插入时 Java 传 null,数据库生成 ID 后,MP 会自动把新 ID 回填到对象中。

      • IdType.ASSIGN_ID (默认):雪花算法 (Snowflake)。这是 MP 默认的策略。即使你没配置自增,MP 也会在内存中生成一个唯一的 19 位 Long 类型数字作为 ID。这在分布式系统中非常有用,避免了数据库自增锁的性能瓶颈。

  3. @TableField:

    • 字段名映射 :处理 Java 属性名 (userEmail) 和数据库列名 (email_addr) 不一致的情况。

    • 新手避坑:exist = false :有时候你的实体类里需要一个辅助属性(例如 private String fullDescription)用来在前端展示,但数据库表中并没有 这个列。此时必须 加上 @TableField(exist = false),否则 MP 在生成 SQL 时会试图去查 full_description 列,导致报错 Unknown column

2.3 BaseMapper 的泛型魔法

UserInfoMapper.java 中,这一行代码价值千金:

java 复制代码
public interface UserInfoMapper extends BaseMapper<UserInfo> {}
  • 泛型 <UserInfo> 的意义:

    • 这是 MP 智能化的源头。你可能会问:"为什么我什么都没写,它就知道要查 user_info 表,而不是 order_info 表?"

    • 原理揭秘 :MP 在 Spring 启动阶段,会通过 Java 反射机制解析 UserInfoMapper 父接口上的泛型参数。它拿到了 UserInfo.class,然后去扫描这个类上的 @TableName@TableField 等注解。

    • 结果 :MP 就像一个动态的 SQL 拼装工厂,它在内存中自动组装出了标准 SQL 语句(如 SELECT id, username... FROM user_info),并将其注册到 MyBatis 的核心配置中。这就好比 MP 替你连夜写好了 XML 文件。

3. 条件构造器 (Wrapper) - 代码中的重难点

这是新手最容易晕,也是 MP 最灵活、最强大的地方。当你需要摆脱简单的 ID 查询,进行复杂条件筛选时,Wrapper 就是你的 SQL 生成器。

3.1 QueryWrapper (基础版 ------ 并不推荐在生产环境大量使用)

java 复制代码
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("age", 18)
       .like("username", "min")
       .or()
       .gt("age", 30);
  • 逻辑 :上述代码会被翻译成 WHERE (age = 18 AND username LIKE '%min%') OR (age > 30)

  • 致命缺点(魔法值) :你需要手动输入字符串 "age""username"

    • 场景演绎 :假设数据库管理员把 age 字段改成了 user_age。你修改了 Java 实体类的属性名,但你忘记 (或很难)搜索到代码里所有散落的字符串 "age"。编译时一切正常,直到上线后用户点击查询,程序直接崩溃报错 Unknown column 'age'。这被称为"魔法值炸弹"。

3.2 LambdaQueryWrapper (进阶推荐版 ------ 必须掌握)

java 复制代码
LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserInfo::getAge, 18)
       .like(UserInfo::getUsername, "min");
  • 知识点:方法引用 (Method Reference)UserInfo::getAge 是 Java 8 引入的语法糖,它本质上指向了属性本身。

  • 绝对优势

    1. 编译期安全 :如果你手抖写成了 UserInfo::getAggge,IDE 会立刻标红,代码根本无法编译通过。这意味着你永远不会因为拼写错误而导致 SQL 运行失败

    2. 重构友好 :当你在 IDE 中使用重构功能(Refactor -> Rename)将实体类的 age 属性改为 userAge 时,所有使用 UserInfo::getAge 的地方都会自动同步修改。这是字符串硬编码绝对做不到的。

  • 新手建议除非是极其动态的场景(列名本身就是变量),否则请无脑选择 LambdaQueryWrapper!

4. 扩展知识点 (面试与实战必知)

4.1 逻辑删除 (Logical Delete) ------ 数据安全的安全带

代码中出现了 delete_flag。这是企业级开发中的标配。

  • 物理删除 (Physical Delete) :执行 DELETE FROM user WHERE id = 1。数据从硬盘上彻底消失。如果员工手滑误删了重要数据,除了去翻数据库备份(可能是一天前的),别无他法。

  • 逻辑删除 (Logical Delete)

    • 概念 :数据并没有真正消失,只是被打上了"已删除"的标签。通常用 0 表示正常,1 表示删除。

    • MP 的黑科技 :MP 提供了透明化的逻辑删除支持。你只需要在配置文件配置好逻辑删除字段,或者在字段上加 @TableLogic 注解。

    • 效果

      • 当你调用 mapper.deleteById(1) 时,MP 拦截 了这个请求,偷偷把它变成了 UPDATE user SET delete_flag = 1 WHERE id = 1

      • 当你调用 mapper.selectById(1)selectList 时,MP 会自动在 SQL 末尾追加 AND delete_flag = 0,确保你查不到已删除的数据。

    • 价值:既保证了业务上看数据删除了,又保留了数据"尸体"用于审计、数据恢复或历史分析。

4.2 自动填充 (Auto Fill) ------ 告别重复劳动

代码中有 createTimeupdateTime

  • 痛点 :在传统的开发中,每次写 insert 都要记得 user.setCreateTime(new Date()),每次 update 都要记得 user.setUpdateTime(new Date())。只要有一处忘了,数据库里就会出现 null 或旧时间,导致数据不一致。

  • 扩展方案 :MP 提供了 MetaObjectHandler 接口。你可以创建一个配置类实现它,告诉 MP:"每当执行插入操作时,帮我把 createTime 填上当前时间;每当更新时,帮我更新 updateTime"。这样,业务代码中再也不用出现时间设置的代码,既干净又可靠。

4.3 为什么不要在循环中调用数据库?(N+1 问题)

在测试代码 testSelectByIds 中,使用的是 selectBatchIds(批量查询)。这是一个非常重要的性能知识点。

  • 错误示范 (新手常犯)

    java 复制代码
    List<Long> ids = Arrays.asList(1L, 2L, 3L, ..., 1000L);
    for (Long id : ids) {
        // 灾难现场:在循环里调用 DAO/Mapper
        mapper.selectById(id); 
    }
  • 后果推演

    • 网络开销 :每一次 selectById 都意味着一次【建立连接 -> 发送 SQL -> 数据库执行 -> 返回结果 -> 关闭连接/归还连接池】的全过程。如果 List 有 1000 个 ID,你就与数据库进行了 1000 次网络交互。这比 SQL 执行本身慢得多。

    • 连接池耗尽:高并发下,这种代码会瞬间占满数据库连接池,导致其他用户的请求被阻塞,系统瘫痪。

  • 正确做法 :使用 selectBatchIds

    • 它会生成一条 SQL:SELECT ... FROM ... WHERE id IN (1, 2, 3, ...)

    • 效率:1 次网络交互 vs 1000 次网络交互。效率提升是数量级的。

5. 如何阅读接下来的代码?

  1. 看注释 :我为几乎每一行代码都添加了详细的 // 注释,不仅解释这行代码在做什么,还解释了为什么要这么写。

  2. 关注 Import :新手常忽略 import 部分,导致自己写的时候找不到类。请特别留意 com.baomidou.mybatisplus 包下的类,不要误引入了其他包的同名类。

  3. 动手尝试 :代码中的 testLambdaQueryWrapper 是最精华的部分,建议多读几遍,体会链式调用的优雅。

数据库初始化

sql 复制代码
-- --------------------------------------------------------
-- 1. 创建数据库
-- IF EXISTS 是为了防止数据库已存在报错
-- DEFAULT CHARACTER SET utf8mb4 是为了支持 Emoji 表情等特殊字符
-- --------------------------------------------------------
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 切换到刚创建的数据库
USE mybatis_test;

-- --------------------------------------------------------
-- 2. 创建用户表 (user_info)
-- --------------------------------------------------------
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID,自增',
    `username` VARCHAR(127) NOT NULL COMMENT '用户名',
    `password` VARCHAR(127) NOT NULL COMMENT '密码',
    `age` TINYINT(4) NOT NULL COMMENT '年龄',
    `gender` TINYINT(4) DEFAULT '0' COMMENT '性别:0-默认,1-男,2-女',
    `phone` VARCHAR(15) DEFAULT NULL COMMENT '手机号',
    `delete_flag` TINYINT(4) DEFAULT '0' COMMENT '逻辑删除标志:0-正常,1-已删除',
    `create_time` DATETIME DEFAULT NOW() COMMENT '创建时间,默认当前时间',
    `update_time` DATETIME DEFAULT NOW() COMMENT '更新时间,默认当前时间',
    PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- --------------------------------------------------------
-- 3. 插入初始化测试数据
-- --------------------------------------------------------
INSERT INTO user_info (username, password, age, gender, phone) VALUES 
('admin', 'admin', 18, 1, '18612340001'),
('zhangsan', 'zhangsan', 18, 1, '18612340002'),
('lisi', 'lisi', 18, 1, '18612340003'),
('wangwu', 'wangwu', 18, 1, '18612340004');

实体类

java 复制代码
package com.bite.mybatis.plus.entity;

// 导入 Lombok 的注解,用于自动生成 getter/setter/toString 等方法
import lombok.Data;
// 导入 MyBatis-Plus 的注解
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;

import java.util.Date;

/**
 * 实体类:对应数据库中的表 user_info
 * * @Data: Lombok 注解。
 * 只要加了这个注解,编译后会自动生成所有属性的:
 * getXXX(), setXXX(), toString(), equals(), hashCode() 方法。
 * 省去了手动编写这些样板代码的麻烦。
 * * @TableName("user_info"): MyBatis-Plus 注解。
 * 告诉框架,这个 UserInfo 类对应数据库里的 "user_info" 表。
 * 如果不加这个注解,MP 默认会把类名转换成下划线形式(user_info)去查找表,
 * 但显式写出来更清晰,也防止类名修改后找不到表。
 */
@Data
@TableName("user_info")
public class UserInfo {

    /**
     * @TableId: 标识这是主键字段。
     * value = "id": 对应数据库表中的列名 "id"。
     * type = IdType.AUTO: 指定主键生成策略。
     * AUTO 代表数据库自增 (Auto Increment)。这意味着插入数据时,
     * Java 代码不需要设置 id 的值,数据库会自动生成。
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id; // 使用 Integer 而不是 int,因为 id 可能为 null (虽然主键一般不为空,但在插入前是空的)

    // 用户名,对应数据库 username 字段
    private String username;

    // 密码,对应数据库 password 字段
    private String password;

    // 年龄
    private Integer age;

    // 性别
    private Integer gender;

    // 手机号
    private String phone;

    /**
     * @TableField("delete_flag"): 映射非主键字段。
     * 场景:如果 Java 属性名叫 deleteFlag (驼峰),数据库列名叫 delete_flag (下划线),
     * MP 其实会自动映射。
     * 但如果数据库列名叫 is_deleted,属性名叫 deleteFlag,就必须用此注解指定:
     * @TableField("is_deleted")
     * * 这里显式写出来是为了演示该注解的用法。
     */
    @TableField("delete_flag")
    private Integer deleteFlag;

    // 创建时间
    private Date createTime;

    // 更新时间
    private Date updateTime;
}

Mapper接口

java 复制代码
package com.bite.mybatis.plus.mapper;

// 导入 MyBatis-Plus 的基础 Mapper 接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入注解和常量,用于自定义 SQL
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.bite.mybatis.plus.entity.UserInfo;

import java.util.List;

/**
 * Mapper 接口
 * * 1. @Mapper: 这是 Spring/MyBatis 的注解,标记这是一个 Mapper 组件,
 * Spring Boot 启动时会扫描到它,并创建代理对象注入到容器中。
 * * 2. extends BaseMapper<UserInfo>: 这是 MP 最核心的用法!
 * 继承了 BaseMapper,并指定泛型为 <UserInfo>。
 * 结果:UserInfoMapper 瞬间自动拥有了针对 UserInfo 表的
 * insert, delete, update, select 等一系列 CRUD 方法。
 * 不需要写任何 XML,也不需要写任何实现类。
 */
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {

    // =========================================================
    // 以下是 PDF 3.4 节提到的 "自定义 SQL" 示例
    // 当 MP 内置的方法满足不了极其复杂的业务需求时,我们可以结合 Wrapper 和手写 SQL
    // =========================================================

    /**
     * 示例 1: 使用注解方式自定义查询
     * * ${ew.customSqlSegment}: 
     * ew 是 EntityWrapper 的缩写(参数名必须叫 ew 或者用 @Param 指定)。
     * customSqlSegment 是 Wrapper 对象根据你 Java 代码生成的 SQL 片段(例如 "WHERE age > 18")。
     * 注意这里用的是 ${} 进行字符串拼接,因为 Wrapper 生成的是 SQL 关键字,不能用 #{} 预编译。
     */
    @Select("select id, username, password, age FROM user_info ${ew.customSqlSegment}")
    List<UserInfo> queryUserByCustom(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);

    /**
     * 示例 2: 对应 XML 配置方式 (假设有 XML 文件)
     * 这里只定义接口,XML 内容请参考 PDF 页面 16
     */
    List<UserInfo> queryUserByCustom2(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);

    /**
     * 示例 3: 自定义更新 SQL
     * * 场景:把某些用户的年龄增加指定的值 (age = age + ?)。
     * MP 内置的 update 主要是覆盖值,这种基于原值的运算适合自定义 SQL。
     * * #{addAge}: 这是一个普通参数,会被预编译成 ?,防止 SQL 注入。
     * ${ew.customSqlSegment}: 这是 Wrapper 生成的条件部分 (例如 "WHERE id IN (1,2,3)")。
     */
    @Update("UPDATE user_info SET age = age + #{addAge} ${ew.customSqlSegment}")
    void updateUserByCustom(@Param("addAge") int addAge, @Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
}

测试类(核心逻辑)

java 复制代码
package com.bite.mybatis.plus;

// 导入测试相关的类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

// 导入 MP 的条件构造器
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;

// 导入我们的实体和 Mapper
import com.bite.mybatis.plus.entity.UserInfo;
import com.bite.mybatis.plus.mapper.UserInfoMapper;

import java.util.List;
import java.util.Arrays; // 注意:PDF里用了 List.of (Java 9+),这里兼容性起见可能用到 Arrays.asList

/**
 * 单元测试类
 * * @SpringBootTest: 启动 Spring Boot 上下文。
 * 这样我们可以使用 @Autowired 注入 Mapper,像在真实的 Controller 里一样运行代码。
 */
@SpringBootTest
class MybatisPlusDemoApplicationTests {

    // 依赖注入:将 Spring 容器管理的 UserInfoMapper 实例注入进来
    @Autowired
    private UserInfoMapper userInfoMapper;

    // =========================================================
    // 2.3 基础 CRUD 测试 (对应 PDF 第 6 页)
    // =========================================================

    /**
     * 测试插入 (Insert)
     * MP 会自动根据实体类属性生成 SQL: INSERT INTO user_info (username, ...) VALUES (...)
     */
    @Test
    void testInsert() {
        System.out.println("----- 测试插入 -----");
        UserInfo user = new UserInfo();
        user.setUsername("bite"); // 设置用户名
        user.setPassword("123456"); // 设置密码
        user.setAge(11);
        user.setGender(0);
        user.setPhone("18610001234");
        // 注意:我们没有设置 ID,因为配置了 AUTO 自增,数据库会处理

        // 调用 BaseMapper 提供的 insert 方法
        int result = userInfoMapper.insert(user);
        
        // 扩展知识:插入成功后,MP 会自动把数据库生成的 ID 回填到 user 对象中
        System.out.println("影响行数: " + result);
        System.out.println("插入后的主键ID: " + user.getId()); 
    }

    /**
     * 测试根据 ID 查询
     */
    @Test
    void testSelectById() {
        System.out.println("----- 测试根据 ID 查询 -----");
        // 这里的 1L 表示 Long 类型,或者直接写 1
        UserInfo user = userInfoMapper.selectById(1);
        System.out.println("查询结果: " + user);
    }

    /**
     * 测试批量查询 ID (WHERE id IN (...))
     */
    @Test
    void testSelectByIds() {
        System.out.println("----- 测试批量查询 -----");
        // List.of 是 Java 9+ 语法,如果报错请换成 Arrays.asList(1, 2, 3, 4)
        List<Integer> ids = Arrays.asList(1, 2, 3, 4);
        List<UserInfo> users = userInfoMapper.selectBatchIds(ids);
        
        // 使用 Lambda 表达式打印结果
        users.forEach(System.out::println);
    }

    /**
     * 测试根据 ID 更新
     * 注意:MP 的 updateById 是"动态 SQL",它只会更新你设置了非空值的字段。
     * 如果你只 setPassword,SQL 就只会 UPDATE ... SET password = ? ...
     * 这里的 age、phone 等其他字段不会被修改为 null。
     */
    @Test
    void testUpdateById() {
        System.out.println("----- 测试更新 -----");
        UserInfo user = new UserInfo();
        user.setId(1); // 必须指定 ID,否则 MP 不知道更新哪条
        user.setPassword("4444444"); // 只更新密码
        
        userInfoMapper.updateById(user);
    }

    /**
     * 测试根据 ID 删除
     */
    @Test
    void testDelete() {
        System.out.println("----- 测试删除 -----");
        userInfoMapper.deleteById(5); // 删除 ID 为 5 的记录
    }

    // =========================================================
    // 3.3 条件构造器测试 (Wrapper) - 重点知识!
    // =========================================================

    /**
     * 3.3.1 QueryWrapper (普通查询构造器)
     * 需求:SELECT ... WHERE age = 18 AND username LIKE '%min%'
     */
    @Test
    void testQueryWrapper() {
        System.out.println("----- QueryWrapper 测试 -----");
        
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        
        // 链式调用
        wrapper.select("id", "username", "password", "age") // 指定查询哪些列,不查全部
               .eq("age", 18)          // eq: equal (等于) -> age = 18
               .like("username", "min"); // like: 模糊查询 -> username like '%min%'
        
        List<UserInfo> userInfos = userInfoMapper.selectList(wrapper);
        userInfos.forEach(System.out::println);
    }

    /**
     * QueryWrapper 用于更新
     * 需求:UPDATE user_info SET delete_flag = 1 WHERE age < 20
     */
    @Test
    void testUpdateByQueryWrapper() {
        System.out.println("----- QueryWrapper 更新测试 -----");
        
        // 1. 定义更新的条件 (WHERE age < 20)
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.lt("age", 20); // lt: less than (小于)
        
        // 2. 定义要更新成什么样 (SET delete_flag = 1)
        UserInfo userInfo = new UserInfo();
        userInfo.setDeleteFlag(1);
        
        // 执行:把符合 wrapper 条件的数据,更新为 userInfo 里的样子
        userInfoMapper.update(userInfo, wrapper);
    }

    /**
     * 3.3.2 UpdateWrapper (专门用于更新的构造器)
     * 场景:不创建 UserInfo 实体对象,直接设置 SET 语句
     * 需求:UPDATE user_info SET delete_flag=0, age=5 WHERE id IN (1,2,3)
     */
    @Test
    void testUpdateByUpdateWrapper() {
        System.out.println("----- UpdateWrapper 测试 -----");
        
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        
        updateWrapper.set("delete_flag", 0)  // 显式设置 SET 字段
                     .set("age", 5)
                     .in("id", Arrays.asList(1, 2, 3)); // WHERE id IN ...
        
        userInfoMapper.update(null, updateWrapper); // 第一个参数传 null,因为 Set 子句已经在 wrapper 里了
    }

    /**
     * UpdateWrapper 执行 SQL 片段
     * 需求:UPDATE user_info SET age = age + 10 WHERE id IN (1,2,3)
     */
    @Test
    void testUpdateBySQLUpdateWrapper() {
        System.out.println("----- UpdateWrapper SQL 片段测试 -----");
        
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.setSql("age = age + 10") // 直接拼接 SQL 片段,适合做数学运算
                     .in("id", Arrays.asList(1, 2, 3));
        
        userInfoMapper.update(null, updateWrapper);
    }

    // =========================================================
    // 3.3.3 LambdaQueryWrapper (推荐使用!!)
    // 优点:防手抖。不用写 "age" 字符串,而是用 UserInfo::getAge 方法引用。
    // 如果实体类属性名改了,这里会自动报错,而不是运行时才报错。
    // =========================================================

    @Test
    void testLambdaQueryWrapper() {
        System.out.println("----- LambdaQueryWrapper 测试 (强力推荐) -----");
        
        // 写法 1:new 对象
        // LambdaQueryWrapper<UserInfo> lambda = new LambdaQueryWrapper<>();
        
        // 写法 2:通过 QueryWrapper 转 lambda (更常用)
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        
        List<UserInfo> list = userInfoMapper.selectList(wrapper.lambda()
                .select(UserInfo::getUsername, UserInfo::getPassword, UserInfo::getAge) // 看着很长,但全是IDE自动补全的,很安全
                .eq(UserInfo::getId, 1) // WHERE id = 1
        );
        
        list.forEach(System.out::println);
    }

    /**
     * 3.3.4 LambdaUpdateWrapper
     */
    @Test
    void testLambdUpdateByUpdateWrapper() {
        System.out.println("----- LambdaUpdateWrapper 测试 -----");
        
        LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
        
        updateWrapper.set(UserInfo::getDeleteFlag, 0) // SET delete_flag = 0
                     .set(UserInfo::getAge, 5)        // SET age = 5
                     .in(UserInfo::getId, Arrays.asList(1, 2, 3)); // WHERE id IN (1,2,3)
        
        userInfoMapper.update(null, updateWrapper);
    }
    
    // =========================================================
    // 3.4 自定义 SQL 测试
    // =========================================================
    
    @Test
    void testQueryUserByCustom() {
        System.out.println("----- 自定义 SQL 测试 -----");
        
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("username", "admin");
        
        // 调用 Mapper 中我们自己定义的 queryUserByCustom 方法
        // 此时 wrapper 生成的 SQL 会自动填充到 ${ew.customSqlSegment} 中
        List<UserInfo> result = userInfoMapper.queryUserByCustom(wrapper);
        result.forEach(System.out::println);
    }
    
    @Test
    void updateUserByCustom() {
        System.out.println("----- 自定义 Update SQL 测试 -----");
        
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.in("id", Arrays.asList(1, 2, 3));
        
        // 将年龄统一增加 10 岁
        userInfoMapper.updateUserByCustom(10, wrapper);
    }
}

以上为您生成了完整的学习资料,建议您按照 Guide 文档 -> 实体类 -> Mapper -> 测试类 的顺序阅读,祝您学习愉快!


为了让你能顺利运行,我为你补充了 3 个必不可少的配置文件

  1. pom.xml: 项目的"购物清单"。告诉 Maven 我们需要下载 Spring Boot、MyBatis-Plus、MySQL 驱动等所有依赖。

  2. application.yml: 项目的"遥控器"。配置你的数据库账号密码。

  3. MybatisPlusApplication.java: 项目的"启动钥匙"。Spring Boot 的主启动类。

  4. README_Run_Guide.md: 保姆级运行手册。一步步教你在 IDEA 里怎么操作。

请按照下面的 README 手册 一步步操作,保证能跑通!


如何运行本项目 (保姆级教程).md

MyBatis-Plus 实战项目运行指南

你好!请按照以下步骤,在你的电脑桌面上搭建并运行这个项目。

第一步:环境准备 (必看)

在开始之前,请确保你的电脑安装了以下软件:

  1. JDK: 推荐 JDK 17 或 JDK 8。

  2. IntelliJ IDEA: 推荐使用社区版 (Community) 或 旗舰版 (Ultimate)。

  3. MySQL 数据库 : 确保数据库服务已开启,且记得你的账号密码

  4. Maven: 通常 IDEA 自带了 Maven,不需要额外安装。

第二步:搭建工程骨架

  1. 新建文件夹:

    • 桌面 上新建一个文件夹,命名为 mybatis-plus-demo
  2. 用 IDEA 打开:

    • 启动 IDEA,点击 Open (打开),选择桌面上的 mybatis-plus-demo 文件夹。
  3. 创建标准目录结构:

    • 在 IDEA 左侧的项目栏中,右键点击项目名,按照以下结构新建文件夹(Directory):

      mybatis-plus-demo
      ├── src
      │ ├── main
      │ │ ├── java
      │ │ │ └── com
      │ │ │ └── bite
      │ │ │ └── mybatis
      │ │ │ └── plus
      │ │ │ ├── entity (存放 UserInfo.java)
      │ │ │ └── mapper (存放 UserInfoMapper.java)
      │ │ └── resources (存放 application.yml)
      │ └── test
      │ └── java
      │ └── com
      │ └── bite
      │ └── mybatis
      │ └── plus (存放 MybatisPlusTests.java)
      └── pom.xml (直接放在最外层)

第三步:复制文件 (核心步骤)

请将我生成的代码复制到对应的位置:

  1. pom.xml:

    • 在项目最外层新建 pom.xml 文件。

    • 复制 我刚才提供的 pom.xml 代码进去。

    • 重要 :复制完后,点击 IDEA 右上角出现的一个小图标(类似于"M"形状的刷新按钮),或者右键 pom.xml 选择 Maven -> Reload Project。这会自动下载依赖包(需要联网)。

  2. application.yml:

    • src/main/resources 目录下新建 application.yml

    • 复制 代码进去。

    • 修改密码 :找到 password: root 这一行,把 root 改成你本地 MySQL 的真实密码!(如果是空密码就留空,但通常不建议)。

  3. Java 代码:

    • UserInfo.java : 放入 src/main/java/com/bite/mybatis/plus/entity

    • UserInfoMapper.java : 放入 src/main/java/com/bite/mybatis/plus/mapper

    • MybatisPlusApplication.java : 放入 src/main/java/com/bite/mybatis/plus (这是启动类,放在包的最外层)

    • MybatisPlusTests.java : 放入 src/test/java/com/bite/mybatis/plus

第四步:初始化数据库

  1. 打开你的数据库工具(如 MySQL Workbench, Navicat, DBeaver, 或 IDEA 自带的 Database 面板)。

  2. 复制 我提供的 schema.sql 文件的内容。

  3. 执行 SQL:全选并在数据库中运行。

    • 这会创建名为 mybatis_test 的数据库。

    • 创建 user_info 表。

    • 插入 4 条初始数据。

第五步:运行测试

现在是最激动人心的时刻!

  1. 打开 src/test/java/.../MybatisPlusTests.java 文件。

  2. 你会看到很多 @Test 标记的方法,比如 testInsert(), testSelectById()

  3. 点击运行

    • 点击方法名左边的小绿三角 ▶️,选择 Run 'testInsert()'
  4. 观察控制台

    • IDEA 下方的控制台会打印出日志。

    • 如果是 testInsert,你会看到 Insert 语句和"插入成功"的日志。

    • 如果是 testSelectById,你会看到查询到的 User 对象。

恭喜你!你已经成功运行了一个企业级的 MyBatis-Plus 项目!

Maven配置文件

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <!-- Spring Boot 父工程,帮我们管理版本号 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version> <!-- 推荐使用较新的稳定版 -->
        <relativePath/> 
    </parent>
    
    <groupId>com.bite</groupId>
    <artifactId>mybatis-plus-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis-plus-demo</name>
    <description>MyBatis-Plus 新手实战项目</description>

    <properties>
        <java.version>17</java.version> <!-- 这里的版本要和你电脑安装的JDK版本一致,如果是JDK8就写1.8 -->
    </properties>

    <dependencies>
        <!-- 1. Spring Boot Web 启动器 (虽然我们是测试,但习惯加上) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 2. MyBatis-Plus 核心依赖 (包含 MyBatis) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.5</version>
        </dependency>

        <!-- 3. MySQL 驱动 (连接数据库用) -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- 4. Lombok (简化代码工具) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 5. 测试依赖 (JUnit 等) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

数据库配置

XML 复制代码
# 数据库连接配置
spring:
  datasource:
    # 数据库驱动,新版 MySQL 驱动类名
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库 URL
    # jdbc:mysql://IP地址:端口/数据库名
    # useSSL=false: 关闭 SSL 安全连接(开发环境通常关闭以避免报错)
    # serverTimezone=Asia/Shanghai: 设置时区,防止时间差 8 小时
    # allowPublicKeyRetrieval=true: 允许公钥检索(解决某些连接错误)
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    # 数据库账号
    username: root
    # 数据库密码 【!!!请修改这里为你自己的密码!!!】
    password: root

# MyBatis-Plus 配置
mybatis-plus:
  configuration:
    # 开启控制台 SQL 日志打印
    # 这样你运行测试时,控制台会打印出实际执行的 SQL 语句,非常适合学习!
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 逻辑删除配置:1 表示已删除,0 表示未删除
      logic-delete-value: 1
      logic-not-delete-value: 0

Spring Boot启动类

java 复制代码
package com.bite.mybatis.plus;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 项目的启动类
 * @SpringBootApplication: 标记这是一个 Spring Boot 应用
 * @MapperScan: 扫描 Mapper 接口所在的包
 * 告诉 MP 去哪里找我们定义的 UserInfoMapper。
 * 如果不加这个注解,或者包路径写错了,会报错 "Field userInfoMapper in ... required a bean of type ..."
 */
@SpringBootApplication
@MapperScan("com.bite.mybatis.plus.mapper")
public class MybatisPlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }

}

好的,所有的材料都准备齐全了。请按照 README_Run_Guide.md 中的步骤开始你的实战之旅吧!如果在运行过程中遇到任何红色报错,随时把报错信息发给我,我来帮你诊断。加油!




太棒了!Exit code 0 代表程序完美运行,没有任何报错。恭喜你,你已经正式跨过了 MyBatis-Plus 的门槛!

这不仅仅是一堆日志,这其实是 MyBatis-Plus (MP) 在向你汇报工作。这个项目通过一系列测试,模拟了真实开发中对数据库的各种操作。

让我们像看"体检报告"一样,根据你控制台打印出的日志,一条条解读 这个项目到底干了啥

1. 批量修改数据 (UpdateWrapper)

  • 日志证据

    Plaintext

    java 复制代码
    ----- UpdateWrapper 测试 -----
    ==>  Preparing: UPDATE user_info SET delete_flag=?,age=? WHERE (id IN (?,?,?))
    ==> Parameters: 0(Integer), 5(Integer), 1(Integer), 2(Integer), 3(Integer)
    <==    Updates: 3
  • 解读 :MP 帮你把 ID 为 1, 2, 3 的用户,一次性把 delete_flag 改为了 0,age 改为了 5。

  • 牛逼之处 :你没写 WHERE id IN 这种复杂的 SQL,是 Java 代码自动生成的。

2. 数学运算更新 (SQL 片段)

  • 日志证据

    Plaintext

    java 复制代码
    ----- UpdateWrapper SQL 片段测试 -----
    ==>  Preparing: UPDATE user_info SET age = age + 10 WHERE (id IN (?,?,?))
  • 解读:它给 ID 为 1, 2, 3 的用户,年龄统统加了 10 岁。这展示了 MP 允许你在自动生成的 SQL 里夹带"私货"(自定义 SQL 片段)。

3. 单条更新 (UpdateById)

  • 日志证据

    Plaintext

    java 复制代码
    ----- 测试更新 -----
    ==>  Preparing: UPDATE user_info SET password=? WHERE id=?
    ==> Parameters: 4444444(String), 1(Integer)
  • 解读 :专门把 ID 为 1 的用户(admin)的密码改成了 "4444444"。注意,它只更新了密码,其他字段没动,这就是"动态 SQL"的智能之处。

4. 精准查询 (LambdaQueryWrapper)

  • 日志证据

    Plaintext

    java 复制代码
    ----- LambdaQueryWrapper 测试 (强力推荐) -----
    ==>  Preparing: SELECT username,password,age FROM user_info WHERE (id = ?)
  • 解读 :它去数据库查了 ID 为 1 的用户,但只取了 用户名、密码和年龄这三个字段,没取别的。这是一种优化性能的查询方式。

5. 批量查询 (SelectByIds)

  • 日志证据

    Plaintext

    java 复制代码
    ----- 测试批量查询 -----
    ==>  Preparing: SELECT ... FROM user_info WHERE id IN ( ? , ? , ? , ? )
    <==        Row: 1, admin...
    <==        Row: 2, zhangsan...
    <==        Total: 4
  • 解读:一次性把 ID 为 1, 2, 3, 4 的四个用户全查出来了。这避免了你写循环去查库(N+1 问题)。

6. 条件更新 (QueryWrapper)

  • 日志证据

    Plaintext

    java 复制代码
    ----- QueryWrapper 更新测试 -----
    ==>  Preparing: UPDATE user_info SET delete_flag=? WHERE (age < ?)
    ==> Parameters: 1(Integer), 20(Integer)
    <==    Updates: 4
  • 解读 :它执行了一个业务逻辑------把所有"年龄小于 20 岁"的用户,delete_flag 标记为 1(逻辑删除)。日志显示有 4 个人符合条件被修改了。

7. 删除操作 (Delete)

  • 日志证据

    Plaintext

    java 复制代码
    ----- 测试删除 -----
    ==>  Preparing: DELETE FROM user_info WHERE id=?
    ==> Parameters: 5(Integer)
    <==    Updates: 0
  • 解读:它尝试删除 ID 为 5 的用户。

  • 注意 :这里显示 Updates: 0,说明删除失败了(没删掉任何行)。为什么?因为代码运行顺序的问题,ID 为 5 的用户在这一步还没被插入呢!(看下一步)

8. 插入新用户 (Insert)

  • 日志证据

    Plaintext

    java 复制代码
    ----- 测试插入 -----
    ==>  Preparing: INSERT INTO user_info ( username, ... ) VALUES ( ?, ... )
    ==> Parameters: bite(String), ...
    <==    Updates: 1
    插入后的主键ID: 5
  • 解读 :成功向数据库插入了一个叫 "bite" 的新用户。数据库自动给它分配了 ID 5

  • (所以如果在 "测试删除" 之前运行这一步,删除就会成功了)


🎓 总结

这个项目通过实战向你展示了 MyBatis-Plus 的核心价值观:

  1. 极简主义 :你没有手写一句 INSERTUPDATESELECT 语句,但完成了所有复杂的增删改查。

  2. 安全性 :所有的参数(比如 admin18)都通过 ? 占位符传递,防止了 SQL 注入攻击。

  3. 灵活性:既能全自动(BaseMapper),也能半自动(Wrapper 组装条件),还能搞特权(自定义 SQL)。

现在的你,已经拥有了一个可以随时用来实验数据库操作的"演武场"!



这是一个非常棒的问题!你现在遇到的情况是:代码运行速度太快(几毫秒就跑完了),就像一阵风刮过,你还没来得及眨眼,数据库里的数据可能已经经历了"插入 -> 修改 -> 删除"的一生。

要想亲眼看到数据变化的路径(比如:先变出一条数据,然后年龄变了,最后消失了),你需要让时间"慢下来",或者让程序"停下来"等你查岗。

这里有三种方法,推荐程度由高到低,强烈推荐方法一

方法一:使用 IDEA 的"断点调试" (Debug 模式) ------ 上帝视角 🕵️‍♂️

这是程序员最常用的方法,就像给程序按下了"暂停键"。

操作步骤:

  1. 打断点 (红点)

    • 打开你的 MybatisPlusTests.java

    • 找到 testUpdateById 方法。

    • userInfoMapper.updateById(user); 这一行的行号左边,用鼠标左键点一下。

    • 你会看到一个 🔴 红色的圆点。这就是断点。

  2. 启动调试 (Debug)

    • 不要点绿色三角形的 Run。

    • 要点 绿色三角形旁边那个 🐞 虫子图标 (Debug)

    • 或者右键方法名,选择 Debug 'testUpdateById()'

  3. 观察变化

    • 程序运行到红点处会自动卡住(暂停)。

    • 此时动作 1 :切换到你的数据库软件(Navicat / DBeaver / IDEA Database),刷新表 user_info。你会发现数据还没变

    • 此时动作 2 :回到 IDEA,按一下 F8 (Step Over) 或者点击调试面板上的 折线箭头(步过)。这代表让程序执行这一行代码。

    • 此时动作 3 :再次切换到数据库软件,刷新表。见证奇迹! 数据变了!

通过这种方式,你可以一步步控制代码执行,每走一步就去数据库看一眼,完全掌握数据变化的每一个瞬间。


方法二:手动"单步执行" ------ 稳扎稳打 🐢

如果你觉得 Debug 有点复杂,可以笨办法:不要运行整个类,而是手动一个个运行方法,并配合数据库刷新。

前提 :先去数据库执行 TRUNCATE TABLE user_info; 把表清空,或者恢复到初始状态。

操作步骤:

  1. 第一步:只运行插入

    • 找到 testInsert 方法。

    • 点击左边绿色箭头 -> Run 'testInsert()'.

    • 看数据库:刷新,你会看到多了一行数据(假设 ID 是 5)。

  2. 第二步:只运行更新

    • 找到 testUpdateById 方法。

    • 关键修改 :把代码里的 user.setId(1) 改成刚才插入的 ID(比如 user.setId(5))。

    • 点击左边绿色箭头 -> Run 'testUpdateById()'.

    • 看数据库:刷新,ID 为 5 的用户密码变了。

  3. 第三步:只运行删除

    • 找到 testDelete 方法。

    • 修改 ID 为 5。

    • 运行 -> 看数据库 :刷新,ID 为 5 的用户 delete_flag 变成了 1(逻辑删除)或者行消失了(物理删除)。


方法三:在代码里"强行睡觉" (不推荐但直观) 🛌

你可以强行让代码执行完一步后,睡个 10 秒钟,给你留出刷新数据库的时间。

操作步骤:

修改你的测试代码,加上 Thread.sleep

Java

java 复制代码
@Test
void testLifeCycle() throws InterruptedException {
    // 1. 插入
    UserInfo user = new UserInfo();
    user.setUsername("Flash");
    user.setAge(20);
    user.setPassword("123");
    userInfoMapper.insert(user);
    System.out.println(">>> 插入完成!快去数据库看!给你10秒钟...");
    
    Thread.sleep(10000); // 暂停 10 秒

    // 2. 更新
    user.setAge(99);
    userInfoMapper.updateById(user);
    System.out.println(">>> 更新完成!快去数据库看!给你10秒钟...");
    
    Thread.sleep(10000); // 暂停 10 秒

    // 3. 删除
    userInfoMapper.deleteById(user.getId());
    System.out.println(">>> 删除完成!去看看还在不在?");
}

运行这个方法,你就有 10 秒钟的时间切屏去数据库刷新查看变化了。


💡 总结建议

作为新手,我强烈建议你尝试方法一(Debug 模式)

虽然它看起来有点高级,但这是程序员最核心的技能之一。学会了 Debug,你不仅能看到数据的变化路径,以后遇到 Bug 也能看到程序是怎么"跑偏"的。

现在,去试着在一个 update 语句上打个红点,然后点那个 🐞 虫子图标试试吧!

相关推荐
码界奇点4 小时前
Java Web学习 第15篇jQuery万字长文详解从入门到实战解锁前端交互新境界
java·前端·学习·jquery
CoderYanger4 小时前
贪心算法:5.最长递增子序列
java·算法·leetcode·贪心算法·1024程序员节
沉迷技术逻辑4 小时前
Docker部署与常用命令
java·docker·eureka
听风吟丶4 小时前
Java HashMap 深度解析:从底层结构到性能优化实战
java·开发语言·性能优化
KakiNakajima4 小时前
浅谈幂等性基本实现原理【kaki备忘录】
java
柯南二号4 小时前
【后端】【Java】一文详解Spring Boot RESTful 接口统一返回与异常处理实践
java·spring boot·状态模式·restful
南龙大魔王4 小时前
spring ai Alibaba(SAA)学习(二)
java·人工智能·spring boot·学习·ai
ZBritney4 小时前
JAVA中的异常二
java·开发语言
汤姆yu5 小时前
基于springboot的运动服服饰销售购买商城系统
java·spring boot·后端