目录
[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 个文件:
-
MyBatisPlus_Guide.md: 核心教程文档。包含对所有知识点的深度解析、扩展说明、背景知识(如 ORM、Lambda 表达式原理)以及"为什么这样写"的解释。请务必配合代码阅读。 -
UserInfo.java: 实体类代码。包含极其详细的注解和字段说明。 -
UserInfoMapper.java: Mapper 接口代码。包含自定义 SQL 的定义。 -
MybatisPlusTests.java: 测试用例代码。这是核心,包含了 PDF 中所有的增删改查、复杂查询、Lambda 构造器等操作,每一行都有详细注释。 -
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 会在启动时自动帮你注入insert、deleteById、updateById、selectById等十几个通用方法的 SQL 实现。对于单表操作,你真的一行 SQL 都不用写!2. 代码中的核心知识点详解与避坑指南
在阅读生成的 Java 代码前,请先掌握以下核心概念,这对应了代码中的关键部分。
2.1 实体类 (Entity) 与 Lombok 的最佳实践
在
UserInfo.java中,你会看到@Data注解。
知识点 :Lombok 是一个在 Java 编译阶段生效的工具库。
深层作用 :它不是在运行时通过反射生成代码,而是利用 Java 的注解处理器(Annotation Processing),在代码编译成
.class文件时,自动"织入"了get、set、toString、hashCode、equals等方法的字节码。新手扩展:
为什么要用? 如果没有 Lombok,一个拥有 10 个字段的
UserInfo类,其 Getter/Setter 代码可能占据 100 多行。这不仅让核心逻辑(字段定义)被淹没,而且当你修改字段类型时(比如把int改成Integer),还需要手动去改下面的配套方法,非常容易漏改。注意:使用 Lombok 需要在 IDE(如 IDEA)中安装 Lombok 插件,否则 IDE 无法识别这些自动生成的方法,会提示代码报错。
2.2 注解映射 (Mapping Annotations) ------ MP 是如何"看懂"数据库的
MP 如何知道 Java 类对应数据库哪张表?靠的是约定大于配置 的设计理念以及注解辅助。
@TableName("user_info"):
约定 :默认情况下,MP 采用"驼峰转下划线"的规则。如果你的类名是
UserInfo,MP 会默认去数据库找user_info表。配置 :现实中数据库表名千奇百怪。如果表名是
t_sys_user,或者为了兼容旧系统叫tblUser,这就破坏了约定。此时必须使用@TableName("t_sys_user")显式告诉 MP:"别猜了,就是这张表"。
@TableId(主键策略):
核心作用 :告诉 MP 哪个字段是主键 。这至关重要,因为
updateById、deleteById等方法生成的WHERE子句完全依赖于此。深度扩展(IdType):
IdType.AUTO:数据库自增 。依赖数据库本身的AUTO_INCREMENT特性。插入时 Java 传null,数据库生成 ID 后,MP 会自动把新 ID 回填到对象中。
IdType.ASSIGN_ID(默认):雪花算法 (Snowflake)。这是 MP 默认的策略。即使你没配置自增,MP 也会在内存中生成一个唯一的 19 位 Long 类型数字作为 ID。这在分布式系统中非常有用,避免了数据库自增锁的性能瓶颈。
@TableField:
字段名映射 :处理 Java 属性名 (
userEmail) 和数据库列名 (email_addr) 不一致的情况。新手避坑:
exist = false:有时候你的实体类里需要一个辅助属性(例如private String fullDescription)用来在前端展示,但数据库表中并没有 这个列。此时必须 加上@TableField(exist = false),否则 MP 在生成 SQL 时会试图去查full_description列,导致报错Unknown column。2.3 BaseMapper 的泛型魔法
在
UserInfoMapper.java中,这一行代码价值千金:
javapublic 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 (基础版 ------ 并不推荐在生产环境大量使用)
javaQueryWrapper<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 (进阶推荐版 ------ 必须掌握)
javaLambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserInfo::getAge, 18) .like(UserInfo::getUsername, "min");
知识点:方法引用 (Method Reference) 。
UserInfo::getAge是 Java 8 引入的语法糖,它本质上指向了属性本身。绝对优势:
编译期安全 :如果你手抖写成了
UserInfo::getAggge,IDE 会立刻标红,代码根本无法编译通过。这意味着你永远不会因为拼写错误而导致 SQL 运行失败。重构友好 :当你在 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) ------ 告别重复劳动
代码中有
createTime和updateTime。
痛点 :在传统的开发中,每次写
insert都要记得user.setCreateTime(new Date()),每次update都要记得user.setUpdateTime(new Date())。只要有一处忘了,数据库里就会出现null或旧时间,导致数据不一致。扩展方案 :MP 提供了
MetaObjectHandler接口。你可以创建一个配置类实现它,告诉 MP:"每当执行插入操作时,帮我把createTime填上当前时间;每当更新时,帮我更新updateTime"。这样,业务代码中再也不用出现时间设置的代码,既干净又可靠。4.3 为什么不要在循环中调用数据库?(N+1 问题)
在测试代码
testSelectByIds中,使用的是selectBatchIds(批量查询)。这是一个非常重要的性能知识点。
错误示范 (新手常犯):
javaList<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. 如何阅读接下来的代码?
看注释 :我为几乎每一行代码都添加了详细的
//注释,不仅解释这行代码在做什么,还解释了为什么要这么写。关注 Import :新手常忽略
import部分,导致自己写的时候找不到类。请特别留意com.baomidou.mybatisplus包下的类,不要误引入了其他包的同名类。动手尝试 :代码中的
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');
实体类
javapackage 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接口
javapackage 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); }
测试类(核心逻辑)
javapackage 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 个必不可少的配置文件:
-
pom.xml: 项目的"购物清单"。告诉 Maven 我们需要下载 Spring Boot、MyBatis-Plus、MySQL 驱动等所有依赖。 -
application.yml: 项目的"遥控器"。配置你的数据库账号密码。 -
MybatisPlusApplication.java: 项目的"启动钥匙"。Spring Boot 的主启动类。 -
README_Run_Guide.md: 保姆级运行手册。一步步教你在 IDEA 里怎么操作。
请按照下面的 README 手册 一步步操作,保证能跑通!
如何运行本项目 (保姆级教程).md
MyBatis-Plus 实战项目运行指南
你好!请按照以下步骤,在你的电脑桌面上搭建并运行这个项目。
第一步:环境准备 (必看)
在开始之前,请确保你的电脑安装了以下软件:
JDK: 推荐 JDK 17 或 JDK 8。
IntelliJ IDEA: 推荐使用社区版 (Community) 或 旗舰版 (Ultimate)。
MySQL 数据库 : 确保数据库服务已开启,且记得你的账号 和密码。
Maven: 通常 IDEA 自带了 Maven,不需要额外安装。
第二步:搭建工程骨架
新建文件夹:
- 在桌面 上新建一个文件夹,命名为
mybatis-plus-demo。用 IDEA 打开:
- 启动 IDEA,点击
Open(打开),选择桌面上的mybatis-plus-demo文件夹。创建标准目录结构:
在 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 (直接放在最外层)第三步:复制文件 (核心步骤)
请将我生成的代码复制到对应的位置:
pom.xml:
在项目最外层新建
pom.xml文件。复制 我刚才提供的
pom.xml代码进去。重要 :复制完后,点击 IDEA 右上角出现的一个小图标(类似于"M"形状的刷新按钮),或者右键
pom.xml选择Maven->Reload Project。这会自动下载依赖包(需要联网)。
application.yml:
在
src/main/resources目录下新建application.yml。复制 代码进去。
修改密码 :找到
password: root这一行,把root改成你本地 MySQL 的真实密码!(如果是空密码就留空,但通常不建议)。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第四步:初始化数据库
打开你的数据库工具(如 MySQL Workbench, Navicat, DBeaver, 或 IDEA 自带的 Database 面板)。
复制 我提供的
schema.sql文件的内容。执行 SQL:全选并在数据库中运行。
这会创建名为
mybatis_test的数据库。创建
user_info表。插入 4 条初始数据。
第五步:运行测试
现在是最激动人心的时刻!
打开
src/test/java/.../MybatisPlusTests.java文件。你会看到很多
@Test标记的方法,比如testInsert(),testSelectById()。点击运行:
- 点击方法名左边的小绿三角 ▶️,选择
Run 'testInsert()'。观察控制台:
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启动类
javapackage 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 的核心价值观:
-
极简主义 :你没有手写一句
INSERT、UPDATE或SELECT语句,但完成了所有复杂的增删改查。 -
安全性 :所有的参数(比如
admin,18)都通过?占位符传递,防止了 SQL 注入攻击。 -
灵活性:既能全自动(BaseMapper),也能半自动(Wrapper 组装条件),还能搞特权(自定义 SQL)。
现在的你,已经拥有了一个可以随时用来实验数据库操作的"演武场"!
这是一个非常棒的问题!你现在遇到的情况是:代码运行速度太快(几毫秒就跑完了),就像一阵风刮过,你还没来得及眨眼,数据库里的数据可能已经经历了"插入 -> 修改 -> 删除"的一生。
要想亲眼看到数据变化的路径(比如:先变出一条数据,然后年龄变了,最后消失了),你需要让时间"慢下来",或者让程序"停下来"等你查岗。
这里有三种方法,推荐程度由高到低,强烈推荐方法一:
方法一:使用 IDEA 的"断点调试" (Debug 模式) ------ 上帝视角 🕵️♂️
这是程序员最常用的方法,就像给程序按下了"暂停键"。
操作步骤:
-
打断点 (红点):
-
打开你的
MybatisPlusTests.java。 -
找到
testUpdateById方法。 -
在
userInfoMapper.updateById(user);这一行的行号左边,用鼠标左键点一下。 -
你会看到一个 🔴 红色的圆点。这就是断点。
-
-
启动调试 (Debug):
-
不要点绿色三角形的 Run。
-
要点 绿色三角形旁边那个 🐞 虫子图标 (Debug)。
-
或者右键方法名,选择 Debug 'testUpdateById()'。
-
-
观察变化:
-
程序运行到红点处会自动卡住(暂停)。
-
此时动作 1 :切换到你的数据库软件(Navicat / DBeaver / IDEA Database),刷新表
user_info。你会发现数据还没变。 -
此时动作 2 :回到 IDEA,按一下 F8 (Step Over) 或者点击调试面板上的 折线箭头(步过)。这代表让程序执行这一行代码。
-
此时动作 3 :再次切换到数据库软件,刷新表。见证奇迹! 数据变了!
-
通过这种方式,你可以一步步控制代码执行,每走一步就去数据库看一眼,完全掌握数据变化的每一个瞬间。
方法二:手动"单步执行" ------ 稳扎稳打 🐢
如果你觉得 Debug 有点复杂,可以笨办法:不要运行整个类,而是手动一个个运行方法,并配合数据库刷新。
前提 :先去数据库执行 TRUNCATE TABLE user_info; 把表清空,或者恢复到初始状态。
操作步骤:
-
第一步:只运行插入
-
找到
testInsert方法。 -
点击左边绿色箭头 ->
Run 'testInsert()'. -
看数据库:刷新,你会看到多了一行数据(假设 ID 是 5)。
-
-
第二步:只运行更新
-
找到
testUpdateById方法。 -
关键修改 :把代码里的
user.setId(1)改成刚才插入的 ID(比如user.setId(5))。 -
点击左边绿色箭头 ->
Run 'testUpdateById()'. -
看数据库:刷新,ID 为 5 的用户密码变了。
-
-
第三步:只运行删除
-
找到
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 语句上打个红点,然后点那个 🐞 虫子图标试试吧!