深入浅出 MyBatis-Plus Wrapper:让条件构造更简单高效
在 MyBatis-Plus 中,Wrapper 是构建查询条件、更新条件的核心工具,它能帮助我们摆脱繁琐的 SQL 字符串拼接,通过链式 API 优雅地组装各种复杂条件。本文将系统讲解 Wrapper 的体系结构、核心用法及实战技巧,从基础的 QueryWrapper 到类型安全的 LambdaWrapper,带你全方位掌握 MyBatis-Plus 的条件构造能力。
一、Wrapper 体系:从抽象到具体的条件构造器
Wrapper 是 MyBatis-Plus 中用于构建 SQL 条件的抽象层,其核心设计采用了 "抽象类 + 接口" 的组合模式,形成了一套完整的条件构造体系。理解这套体系的继承关系,能帮助我们更快掌握各类 Wrapper 的适用场景。
1.1 Wrapper 核心继承关系
Wrapper 的顶层结构如下(可通过 IDE 的Ctrl+H
查看类继承树):
Wrapper(最顶层抽象类)
└── AbstractWrapper(查询条件封装,生成WHERE子句)├── QueryWrapper(查询条件构造器,用于SELECT/DELETE)├── UpdateWrapper(更新条件构造器,用于UPDATE)└── AbstractLambdaWrapper(Lambda语法支持)├── LambdaQueryWrapper(Lambda风格的查询构造器)└── LambdaUpdateWrapper(Lambda风格的更新构造器)
1.2 AbstractWrapper 的核心接口
AbstractWrapper 实现了 4 个核心接口,这些接口定义了条件构造的基础能力:
接口 | 作用 |
---|---|
Compare | 提供比较条件方法(eq/ne/gt/lt/between 等),处理属性与值的比较 |
Nested | 支持嵌套条件(通过 and/or 组合子条件),实现复杂逻辑的括号包裹 |
Join | 处理表连接(INNER JOIN/LEFT JOIN 等),支持多表查询时的关联条件 |
Func | 函数式接口,用于封装可复用的条件逻辑,简化代码复用 |
这些接口的方法均为默认方法(Java 8+),子类可直接使用或重写,这也是 Wrapper 能实现链式调用的核心原因。
二、QueryWrapper:查询与删除的条件构造利器
QueryWrapper 是最常用的条件构造器,主要用于 SELECT 查询和 DELETE 删除操作,支持各种条件组合、排序、子查询等场景。
2.1 基础查询:组合多条件
场景:查询用户名包含 "a"、年龄在 20-30 之间、邮箱不为 null 的用户。
@Test
public void testQueryWrapperBasic() {QueryWrapper<People> queryWrapper = new QueryWrapper<>();// 链式调用组装条件queryWrapper.like("username", "a") // 用户名 LIKE '%a%'.between("age", 20, 30) // 年龄 BETWEEN 20 AND 30.isNotNull("email"); // 邮箱 IS NOT NULLList<People> peopleList = peopleMapper.selectList(queryWrapper);peopleList.forEach(System.out::println);
}
生成的 SQL:
SELECT id, username AS name, age, email, is_deleted
FROM t_people
WHERE is_deleted=0
AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
关键点:
- 逻辑删除字段(如
is_deleted
)会自动加入条件(需配置逻辑删除); - 条件默认通过
AND
连接,可通过or()
方法切换为OR
。
2.2 排序:orderBy 系列方法
场景:按年龄降序、ID 升序查询所有用户。
@Test
public void testQueryWrapperOrder() {QueryWrapper<People> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("age") // 按年龄降序.orderByAsc("id"); // 按ID升序List<People> peopleList = peopleMapper.selectList(queryWrapper);peopleList.forEach(System.out::println);
}
生成的 SQL:
SELECT id, username AS name, age, email, is_deleted
FROM t_people
WHERE is_deleted=0
ORDER BY age DESC, id ASC
2.3 构建删除条件
场景:删除邮箱为 null 的用户(逻辑删除)。
@Test
public void testQueryWrapperDelete() {QueryWrapper<People> queryWrapper = new QueryWrapper<>();queryWrapper.isNull("email"); // 邮箱为nullint rows = peopleMapper.delete(queryWrapper);System.out.println("受影响的行数:" + rows);
}
生成的 SQL:
UPDATE t_people SET is_deleted=1
WHERE is_deleted=0 AND (email IS NULL)
说明:若配置了逻辑删除,delete
方法实际执行 UPDATE;若未配置,则执行物理删除(DELETE 语句)。
2.4 条件优先级:括号与逻辑组合
场景:更新满足以下条件的用户:(用户名包含 "a" 且年龄 > 20)或(邮箱为 null)。
@Test
public void testQueryWrapperPriority() {QueryWrapper<People> queryWrapper = new QueryWrapper<>();queryWrapper.like("username", "a").gt("age", 20).or() // 切换为OR连接.isNull("email");People update = new People();update.setAge(18);update.setEmail("people@qcby.com");int rows = peopleMapper.update(update, queryWrapper);System.out.println("受影响的行数:" + rows);
}
生成的 SQL:
UPDATE t_people SET age=?, email=?
WHERE is_deleted=0
AND (username LIKE ? AND age > ? OR email IS NULL)
进阶:若需调整优先级(如 "用户名包含 a 且(年龄 > 20 或邮箱为 null)"),可使用and(i -> ...)
嵌套条件:
queryWrapper.like("username", "a").and(i -> i.gt("age", 20).or().isNull("email")); // 嵌套条件用括号包裹
生成的 SQL 条件:
AND (username LIKE ? AND (age > ? OR email IS NULL))
2.5 自定义查询字段:select 子句
场景:只查询用户名和年龄字段,减少数据传输。
@Test
public void testQueryWrapperSelect() {QueryWrapper<People> queryWrapper = new QueryWrapper<>();queryWrapper.select("username", "age"); // 指定查询字段// 使用selectMaps()返回Map列表(避免实体类未查询字段为null)List<Map<String, Object>> maps = peopleMapper.selectMaps(queryWrapper);maps.forEach(System.out::println);
}
生成的 SQL:
SELECT username, age
FROM t_people
WHERE is_deleted=0
2.6 子查询:inSql 方法
场景:查询 ID 在子查询结果中的用户(子查询:ID>=3 的用户)。
@Testpublic void testQueryWrapperSubQuery() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();// 字段"id"的值在子查询结果中queryWrapper.inSql("uid", "select uid from t_user where uid >= 3");List<User> peopleList = userMapper.selectList(queryWrapper);peopleList.forEach(System.out::println);}
生成的 SQL:
SELECT id, username AS name, age, email, is_deleted
FROM t_people
WHERE is_deleted=0
AND (id IN (select id from t_people where id <= 3))
三、UpdateWrapper:专注更新操作的条件构造器
UpdateWrapper 与 QueryWrapper 的区别在于:它更专注于更新场景,可直接通过set
方法设置字段值,无需额外创建实体类。
3.1 基本用法:组合更新条件与字段
场景:将(年龄 > 20 或邮箱为 null)且用户名包含 "a" 的用户,年龄改为 18,邮箱改为 "user@qcby.com"。
@Test
public void testUpdateWrapper() {UpdateWrapper<People> updateWrapper = new UpdateWrapper<>();updateWrapper.set("age", 18) // 直接设置字段值.set("email", "user@qcby.com").like("username", "a").and(i -> i.gt("age", 20).or().isNull("email")); // 嵌套条件// 第一个参数为null,因为更新字段已通过set()设置int rows = peopleMapper.update(null, updateWrapper);System.out.println("受影响的行数:" + rows);
}
生成的 SQL:
UPDATE t_people SET age=?, email=?
WHERE is_deleted=0
AND (username LIKE ? AND (age > ? OR email IS NULL))
优势:无需创建实体类,直接在 Wrapper 中设置更新字段,适合部分字段更新场景。
四、Condition 参数:简化条件判断逻辑
在实际开发中,条件往往来源于用户输入(可能为 null),需要先判断条件是否有效再组装。MyBatis-Plus 提供了带condition
参数的重载方法,简化这种判断。
4.1 传统方式:大量 if 判断
@Test
public void testConditionTraditional() {String username = null; // 用户未输入用户名Integer ageBegin = 10; // 开始年龄Integer ageEnd = 24; // 结束年龄QueryWrapper<People> queryWrapper = new QueryWrapper<>();// 手动判断条件是否有效if (StringUtils.isNotBlank(username)) {queryWrapper.like("username", username);}if (ageBegin != null) {queryWrapper.ge("age", ageBegin);}if (ageEnd != null) {queryWrapper.le("age", ageEnd);}List<People> peopleList = peopleMapper.selectList(queryWrapper);
}
4.2 优化方式:带 Condition 的链式调用
@Test
public void testConditionOptimized() {String username = null;Integer ageBegin = 10;Integer ageEnd = 24;QueryWrapper<People> queryWrapper = new QueryWrapper<>();// condition为true时才组装该条件queryWrapper.like(StringUtils.isNotBlank(username), "username", username).ge(ageBegin != null, "age", ageBegin).le(ageEnd != null, "age", ageEnd);List<People> peopleList = peopleMapper.selectList(queryWrapper);
}
效果:当condition
为false
时,该条件不会被组装到 SQL 中,避免了大量 if 语句,代码更简洁。
五、LambdaWrapper:类型安全的条件构造
传统 Wrapper 通过字符串指定字段名(如"username"
),存在硬编码问题(字段名修改后编译不报错,运行时才发现)。LambdaWrapper 通过方法引用(如People::getName
)解决这一问题,实现编译时字段校验。
5.1 LambdaQueryWrapper:查询场景
场景:使用 Lambda 风格查询用户名包含 "a"、年龄在 10-24 之间的用户。
@Test
public void testLambdaQueryWrapper() {String username = "a";Integer ageBegin = 10;Integer ageEnd = 24;LambdaQueryWrapper<People> lambdaWrapper = new LambdaQueryWrapper<>();lambdaWrapper// 方法引用:People::getName 对应字段username(需与实体类属性一致).like(StringUtils.isNotBlank(username), People::getName, username).ge(ageBegin != null, People::getAge, ageBegin).le(ageEnd != null, People::getAge, ageEnd);List<People> peopleList = peopleMapper.selectList(lambdaWrapper);
}
优势:
- 字段名通过方法引用指定,编译时检查,避免拼写错误;
- 无需记忆数据库字段名,直接使用实体类属性,降低心智负担。
5.2 LambdaUpdateWrapper:更新场景
场景:使用 Lambda 风格更新用户名包含 "a" 且(年龄 < 24 或邮箱为 null)的用户。
@Test
public void testLambdaUpdateWrapper() {LambdaUpdateWrapper<People> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();lambdaUpdateWrapper.set(People::getAge, 18).set(People::getEmail, "people@qcby.com").like(People::getName, "a").and(i -> i.lt(People::getAge, 24).or().isNull(People::getEmail));int rows = peopleMapper.update(null, lambdaUpdateWrapper);System.out.println("受影响的行数:" + rows);
}
生成的 SQL:
UPDATE t_people SET age=?, email=?
WHERE is_deleted=0
AND (username LIKE ? AND (age < ? OR email IS NULL))