第三章: [进阶] 从实体映射到复杂关联查询
第三章: [进阶] 从实体映射到复杂关联查询
Prorise第三章. [进阶] 实体映射与 Wrapper 条件构造器
摘要:本章我们将攻克 Mybatis-Plus 最核心的难点——如何用 Java 代码写 SQL。我们将从最基础的实体映射注解开始,逐步掌握 Wrapper 条件构造器的每一个常用 API,建立起“Java 方法”与“SQL 语法”的一一映射关系,最后通过 XML 解决多表关联查询的难题。
本章学习路径
- 映射注解:掌握
@TableName、@TableId、@TableField,解决数据库与 Java 命名不一致的问题。 - Wrapper 语法基础:像背单词一样掌握
eq(=)、gt(>)、between等核心 API 的 SQL 映射规则。 - Wrapper 进阶语法:掌握模糊查询
like、逻辑嵌套or/and以及字段投影select的写法。 - 复杂关联:回归 MyBatis XML,解决一对一、一对多等多表关联查询。
3.1. 实体与表映射注解
在开始复杂的查询之前,我们必须先解决一个基础问题:Java 实体类是如何找到数据库中对应的表的?
默认情况下,MP 采用“驼峰转下划线”的规则(如 UserDO -> user_do)。但实际项目中,命名往往不规范,这时就需要注解来帮忙。
3.1.1. 表名与主键映射
场景:数据库表名为 tb_user,但我们的类名是 UserDO;且主键是自增 ID。
文件路径:src/main/java/com/example/mpstudy/domain/UserDO.java
1 | // 1. 显式指定表名 |
3.1.2. 字段映射神器:@TableField
@TableField 是最常用的注解,用于解决属性名与列名不一致,或属性非数据库字段等问题。
1 |
|
常见用法速查:
| 场景 | 注解写法 | 解释 |
|---|---|---|
| 改名 | @TableField("user_email") | Java 叫 email,数据库叫 user_email,需手动映射。 |
| 忽略 | @TableField(exist = false) | tempCode 是临时字段,数据库没这列,必须 忽略,否则报错。 |
| 隐藏 | @TableField(select = false) | password 字段存在,但在 select 查询时默认不查出来,保护隐私。 |
3.1.3. 主键策略:@TableId 与 IdType
主键是表的灵魂。MP 提供了多种主键生成策略,通过 IdType 枚举控制。
| 策略类型 | 枚举值 | 说明 | 适用场景 |
|---|---|---|---|
| 自增 | AUTO | 依赖数据库的 auto_increment 特性。 | 单体应用,开发测试环境。 |
| 雪花算法 | ASSIGN_ID | MP 内置算法,生成 19 位唯一 Long 值。 | 分布式系统默认推荐,无中心高性能。 |
| 手动输入 | INPUT | 插入前必须手动调用 setId()。 | 业务主键(如身份证号、学号)。 |
| UUID | ASSIGN_UUID | 生成 32 位字符串。 | 对长度不敏感且需要字符串主键的场景。 |
代码演示:
1 | // 本教程使用 MySQL 自增主键,方便演示 |
3.2. Wrapper:Java 里的 SQL 翻译官
在原生 MyBatis 中,我们需要在 XML 里写 WHERE age > 18。在 MP 中,我们使用 Wrapper(条件构造器) 来生成这些 SQL。
这就好比学英语,我们需要先掌握“单词量”。
3.2.1. 核心选择:LambdaQueryWrapper
MP 提供了 QueryWrapper(普通版)和 LambdaQueryWrapper(Lambda 版)。
场景:你需要查询 name = "Jack" 的用户。
1 | QueryWrapper<UserDO> wrapper = new QueryWrapper<>(); |
场景:同样的查询,使用 Lambda 语法。
1 | LambdaQueryWrapper<UserDO> wrapper = new LambdaQueryWrapper<>(); |
底层原理:MP 利用了 Java 8 的 SerializedLambda 特性,能在运行时解析 UserDO::getName 这个方法引用,反向推导出它对应的属性名 name,再结合 @TableField 注解找到数据库列名。这就是“类型安全”的魔法。
3.2.2. 基础比较语法 (eq, ne, gt, lt, between)
这是我们最常用的 SQL 语法翻译。请对照下表进行记忆:
| Wrapper 方法 | SQL 含义 | 英文全称 |
|---|---|---|
eq | = | Equal |
ne | <> | Not Equal |
gt | > | Greater Than |
ge | >= | Greater than or Equal |
lt | < | Less Than |
le | <= | Less than or Equal |
between | BETWEEN v1 AND v2 | Between |
实战演示:
我们通过一个测试用例,一次性演练这些基础语法。
文件路径:src/test/java/com/example/mpstudy/WrapperTest.java
1 |
|
1
2
3
4
5
6
7
-- 第一个查询
==> Preparing: SELECT ... FROM tb_user WHERE (name = ? AND age > ?)
==> Parameters: Tom(String), 18(Integer)
-- 第二个查询
==> Preparing: SELECT ... FROM tb_user WHERE (age BETWEEN ? AND ?)
==> Parameters: 20(Integer), 30(Integer)
3.2.3. 模糊查询 (like)
场景:用户搜索框,输入 “J” 搜索名字包含 J 的人。
| Wrapper 方法 | SQL 含义 | 说明 |
|---|---|---|
like | LIKE '%值%' | 包含,全模糊(索引失效) |
likeLeft | LIKE '%值' | 以值结尾 |
likeRight | LIKE '值%' | 以值开头(走索引,推荐) |
代码演示:
1 |
|
3.2.4. 空值与集合查询 (isNull, in)
场景:查询还没有填写邮箱的用户(email 为 null),或者查询 ID 是 1、3、5 的特定用户。
1 |
|
3.3. Wrapper 进阶语法
掌握了基础单词后,我们需要学习如何构造复杂的句子(逻辑嵌套)以及如何只查询部分内容(字段投影)。
3.3.1. 逻辑运算与嵌套 (or, and, nested)
默认情况下,Wrapper 链式调用拼接的都是 AND。
wrapper.eq("A").eq("B")->WHERE A AND B
但如果我们需要 OR,或者 (A AND B) OR C 这种括号逻辑,就需要小心了。
实战演示:
1 |
|
为什么必须用 nested?
如果不加 nested 直接写 lt().isNotNull().or().eq(),根据 SQL 优先级 AND > OR,逻辑会变成 age<20 AND (email不空 OR name是Jone),这通常不是我们想要的业务逻辑。
3.3.2. 字段投影 (select)
场景:用户表有 50 个字段,但前端下拉框只需要 id 和 name。如果查所有字段,性能太差。
1 |
|
3.4. 复杂关联查询 (XML 实现)
讲完了单表 Wrapper,我们来到最痛的地方:多表关联。
Wrapper 极其擅长单表,但对于“一对多”(一个部门有多个用户)或“一对一”(一个用户属于一个部门)的关联查询,回归 XML 才是最清晰的方案。
3.4.1. 场景准备
我们需要模拟一个“部门 (Department)”包含多个“用户 (User)”的场景。
1. 准备数据库
1 | -- 部门表 |
2. 准备 VO (View Object)
我们需要一个对象来承载“部门+用户列表”的数据。
1 |
|
3.4.2. XML <resultMap> 实战
我们要实现的效果是:输入部门 ID,查出部门信息,同时自动查出该部门下的所有员工,填充到 users 列表中。
第一步:定义 Mapper 接口
文件路径:src/main/java/com/example/mpstudy/mapper/DepartmentMapper.java
1 | public interface DepartmentMapper extends BaseMapper<DepartmentDO> { |
第二步:编写 XML 映射 (核心)
文件路径:src/main/resources/mapper/DepartmentMapper.xml
1 | <mapper namespace="com.example.mpstudy.mapper.DepartmentMapper"> |
第三步:测试
1 |
|
3.5. 本章总结与语法速查
3.5.1. Wrapper 语法速查表
| 场景 | Wrapper 方法 | 对应 SQL |
|---|---|---|
| 基础 | eq / ne | = / <> |
| 范围 | gt / ge | > / >= |
| 区间 | between | BETWEEN a AND b |
| 模糊 | like / likeRight | LIKE '%a%' / LIKE 'a%' |
| 空值 | isNull / isNotNull | IS NULL / IS NOT NULL |
| 集合 | in / notIn | IN (a,b,c) / NOT IN ... |
| 逻辑 | or / nested | OR / (...) |
3.5.2. 核心避坑指南
Wrapper 的
.or()陷阱- 现象:
wrapper.eq("A").or().eq("B").eq("C") - 后果:SQL 变成
A OR B AND C。如果你想表达A OR (B AND C)这是对的;但如果你想表达(A OR B) AND C,这绝对是错的。 - 对策:遇到复杂逻辑,务必使用
nested显式加括号,不要依赖默认优先级。
- 现象:
like的索引失效- 现象:
wrapper.like(...)使用的是%value%。 - 后果:全模糊查询不走索引,全表扫描,百万数据直接卡死。
- 对策:优先使用
likeRight(value%),这能命中索引。
- 现象:
多表查询别用 Wrapper
- 建议:虽然网上有 Wrapper 做 Join 的骚操作,但代码可读性极差。请老老实实写 XML,这是对团队协作负责。
下章预告:学会了怎么写查询条件,但如果查出来的数据有 100 万条怎么办?我们不可能一次性全部返回给前端。下一章,我们将解锁 Mybatis-Plus 的插件体系,学习如何实现 物理分页,以及如何利用 乐观锁插件 解决并发更新问题。







