本章从 Wrapper 体系、QueryWrapper、UpdateWrapper、condition、LambdaWrapper 依次展开。
1. Wrapper 体系全景图:你要先记住这 6 个类
Wrapper 这一套的目的:用 Java 链式 API 拼出 SQL 的 where / order by / select / set 等片段。
Wrapper:条件构造抽象父类AbstractWrapper:封装查询条件、生成 SQL 的 where 条件QueryWrapper:查询条件封装UpdateWrapper:更新条件封装LambdaQueryWrapper:Lambda 方式写查询条件(避免字符串字段名写错)LambdaUpdateWrapper:Lambda 方式写更新条件
记忆口诀:Query 查、Update 改;Lambda 负责“字段安全”。
2. QueryWrapper:查询/排序/删除/优先级/指定列/子查询
2.1 组装查询条件(where)
典型写法:like + between + isNotNull(链式调用)
资料示例:查询“用户名包含 a,年龄 20~30,邮箱不为 null”的用户。
常用条件(按使用频率排序):
- 等值:
eq/ne - 范围:
gt/ge/lt/le、between - 模糊:
like/likeLeft/likeRight - 集合:
in/notIn - 空判断:
isNull/isNotNull
2.2 组装排序条件(order by)
示例:年龄降序,年龄相同按 id 升序。
常用:orderByAsc/Desc,也可用 orderBy(true, isAsc, column) 做动态排序。
2.3 用 QueryWrapper 也能构建删除条件(delete where)
示例:删除 email 为空的用户:queryWrapper.isNull("email") 然后 mapper.delete(wrapper)。
重点:Wrapper 不止能查,也能给 delete/update 当条件。
2.4 条件优先级(and/or 的括号问题)
你最容易踩坑的是:链式 or/and 默认按 SQL 规则结合,括号要自己控制。
资料里给了两种 update 条件对比,其中第二种用 .and(i -> ...) 明确括号范围:
.like(...).and(i -> i.gt(...).or().isNull(...))
经验法则:只要你的条件里出现 “A and (B or C)” 这种结构,就优先用
and(consumer)/nested(consumer)把括号写出来。
2.5 组装 select 子句(只查部分列)
示例:只查 username 和 age:queryWrapper.select("username","age"),并配合 selectMaps 返回 Map 列表(避免实体里没查到的字段全是 null)。
2.6 子查询(inSql)
示例:inSql("id", "select id from t_user where id <= 3")
适用:你手里就是一段子查询 SQL,懒得再拆成条件 API。
3. UpdateWrapper:把 “set 子句” 和 “where 条件” 分开写
当你想写这种 SQL:
UPDATE t_user
SET age=18, email='xxx'
WHERE username LIKE '%a%' AND (age > 20 OR email IS NULL)
资料示例对应的链式写法是:先 .set(...) 再拼条件,再 update(...)。
3.1 update(entity, wrapper) vs update(null, wrapper)
资料里特别强调:如果你用 UpdateWrapper.set() 来组装 set 子句,调用更新时可以 update(null, updateWrapper)。
实战建议:
- 只想用 wrapper 的 set:
mapper.update(null, updateWrapper) - 想用实体对象参与 set(非 null 字段会生效) :
mapper.update(entity, wrapper)
4. condition:解决“用户输入是可选条件”的最佳姿势
真实业务里,查询条件很多来自前端输入,可能为空;空条件绝对不能拼进 SQL,否则结果就错。资料里先给了 if 判断写法,再给“带 condition 参数的重载方法”,用于简化。
4.1 传统写法(if 判断)
示例:username 可能为 null,ageBegin/ageEnd 也可能为 null;用 if 决定是否拼条件。
4.2 推荐写法(condition 重载)
直接写成:
.like(StringUtils.isNotBlank(username), "username", "a").ge(ageBegin != null, "age", ageBegin).le(ageEnd != null, "age", ageEnd)
你之后写列表筛选页(用户名/年龄区间/状态/时间范围)基本就靠这一套,代码会干净很多。
5. LambdaQueryWrapper / LambdaUpdateWrapper:避免“字段名写错”的终极方案
Lambda 版的核心价值:字段用方法引用 User::getAge,不再手写字符串 "age" ,IDE 可重构、可编译期发现错误。
资料示例(LambdaQueryWrapper):
.like(..., User::getName, username).ge(..., User::getAge, ageBegin).le(..., User::getAge, ageEnd)
资料示例(LambdaUpdateWrapper):
.set(User::getAge, 18).like(User::getName, "a").and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail))
选择建议:新代码优先 LambdaWrapper,尤其是字段多、会重构的项目。
6. 常用接口(你写 CRUD 最常用的那一批)
在 Mapper 层你最常用的就是 BaseMapper<T>:里面自带 insert/delete/update/select 等通用 CRUD。资料里也明确了很多方法都带 Wrapper 参数:有条件就传 wrapper,没条件就传 null。-L324
你可以把它记成 3 组:
6.1 查(select*)
selectById(id)selectList(wrapper):查列表(最常用)selectOne(wrapper):期望一条(多条会异常)selectCount(wrapper):计数selectMaps(wrapper)/selectObjs(wrapper):只取部分字段时常用selectPage(page, wrapper):分页(后面插件章会细讲)
6.2 改(update*)
updateById(entity)update(entity, wrapper):实体 set + 条件update(null, updateWrapper):wrapper set + 条件
6.3 删(delete*)
deleteById(id)delete(wrapper)(用 QueryWrapper 拼条件)
7. 高频易错点(建议你马上避坑)
- 字段名到底写哪个?
QueryWrapper.like("username", "a")这种写法里传的是数据库字段(示例就是 username),不是实体属性别名。
👉 所以更建议用 LambdaWrapper:User::getName。 - or/and 没加括号导致逻辑错
出现混合条件就用.and(i -> ...)/.nested(i -> ...)固化括号。 - 只查部分列却用实体接收,结果一堆 null
只 select 部分列时优先selectMaps。 - 动态条件不要写一堆 if
直接用 condition 重载,代码更短更稳。