MyBatis-Plus条件构造器QueryWrapper深度指南:告别手写SQL的烦恼
引言:为什么需要QueryWrapper?
在日常Java开发中,SQL编写一直是开发者面临的痛点之一。传统的MyBatis虽然提供了强大的SQL映射能力,但仍然需要手动编写大量SQL语句,这不仅容易出错,还难以维护。MyBatis-Plus的QueryWrapper条件构造器正是为了解决这一痛点而生,它通过链式API让开发者能够以面向对象的方式构建复杂的查询条件,彻底告别手写SQL的烦恼。
通过本文,你将掌握:
- QueryWrapper的核心概念和设计哲学
- 各种条件方法的详细用法和最佳实践
- 高级嵌套查询和复杂条件构建技巧
- 性能优化和常见陷阱避免
- 实际项目中的综合应用案例
一、QueryWrapper核心架构解析
1.1 类层次结构
QueryWrapper继承体系采用了经典的模板方法模式,其核心类关系如下:
1.2 核心设计理念
QueryWrapper的设计遵循以下几个重要原则:
- 链式调用:所有方法都返回this,支持流畅的API调用
- 条件参数化:自动处理SQL参数绑定,防止SQL注入
- 类型安全:通过泛型确保类型一致性
- 可组合性:支持复杂的嵌套条件组合
二、基础条件方法详解
2.1 比较操作符
QueryWrapper提供了完整的SQL比较操作符支持:
// 等于查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三");
// SQL: WHERE name = '张三'
// 不等于查询
wrapper.ne("age", 18);
// SQL: WHERE name = '张三' AND age <> 18
// 大于小于查询
wrapper.gt("create_time", "2023-01-01")
.lt("create_time", "2023-12-31");
// SQL: WHERE name = '张三' AND age <> 18
// AND create_time > '2023-01-01' AND create_time < '2023-12-31'
// 范围查询
wrapper.between("age", 18, 30);
// SQL: WHERE name = '张三' AND age <> 18
// AND create_time > '2023-01-01' AND create_time < '2023-12-31'
// AND age BETWEEN 18 AND 30
2.2 模糊查询
模糊查询是业务系统中的常见需求,QueryWrapper提供了多种模糊匹配方式:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 全模糊匹配
wrapper.like("name", "张");
// SQL: WHERE name LIKE '%张%'
// 左模糊匹配
wrapper.likeLeft("name", "三");
// SQL: WHERE name LIKE '%三'
// 右模糊匹配
wrapper.likeRight("name", "张");
// SQL: WHERE name LIKE '张%'
// 不包含匹配
wrapper.notLike("name", "测试");
// SQL: WHERE name NOT LIKE '%测试%'
2.3 集合操作
对于IN和NOT IN查询,QueryWrapper提供了灵活的支持:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// IN查询 - 集合方式
List<Integer> ages = Arrays.asList(18, 20, 25);
wrapper.in("age", ages);
// SQL: WHERE age IN (18, 20, 25)
// IN查询 - 数组方式
wrapper.in("status", 1, 2, 3);
// SQL: WHERE status IN (1, 2, 3)
// NOT IN查询
wrapper.notIn("role_id", 4, 5, 6);
// SQL: WHERE role_id NOT IN (4, 5, 6)
// 空集合处理
List<Integer> emptyList = Collections.emptyList();
wrapper.in("department_id", emptyList);
// SQL: WHERE department_id IN () - 注意这会返回空结果
2.4 空值判断
空值处理是数据库查询中的常见场景:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// IS NULL查询
wrapper.isNull("email");
// SQL: WHERE email IS NULL
// IS NOT NULL查询
wrapper.isNotNull("phone");
// SQL: WHERE email IS NULL AND phone IS NOT NULL
// allEq方法处理空值
Map<String, Object> params = new HashMap<>();
params.put("name", "张三");
params.put("age", null);
params.put("email", "zhangsan@example.com");
wrapper.allEq(params, true); // 第二个参数表示null是否转换为IS NULL
// SQL: WHERE name = '张三' AND age IS NULL AND email = 'zhangsan@example.com'
三、高级查询技巧
3.1 嵌套查询
嵌套查询是构建复杂条件的关键能力:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// AND嵌套
wrapper.and(i -> i.eq("status", 1).gt("score", 90));
// SQL: WHERE (status = 1 AND score > 90)
// OR嵌套
wrapper.or(i -> i.eq("type", "VIP").eq("level", 5));
// SQL: WHERE (status = 1 AND score > 90) OR (type = 'VIP' AND level = 5)
// 复杂多层嵌套
wrapper.nested(i -> i.eq("department", "技术部")
.and(j -> j.gt("work_years", 3).or(k -> k.eq("title", "高级工程师"))));
// SQL: WHERE ((status = 1 AND score > 90) OR (type = 'VIP' AND level = 5))
// AND (department = '技术部' AND (work_years > 3 OR title = '高级工程师'))
3.2 动态条件构建
在实际业务中,经常需要根据参数动态构建查询条件:
public List<User> searchUsers(UserQuery query) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 动态添加条件
if (StringUtils.isNotBlank(query.getName())) {
wrapper.like("name", query.getName());
}
if (query.getMinAge() != null) {
wrapper.ge("age", query.getMinAge());
}
if (query.getMaxAge() != null) {
wrapper.le("age", query.getMaxAge());
}
if (CollectionUtils.isNotEmpty(query.getStatusList())) {
wrapper.in("status", query.getStatusList());
}
// 排序条件
if (StringUtils.isNotBlank(query.getOrderBy())) {
if ("desc".equalsIgnoreCase(query.getOrderType())) {
wrapper.orderByDesc(query.getOrderBy());
} else {
wrapper.orderByAsc(query.getOrderBy());
}
}
return userMapper.selectList(wrapper);
}
3.3 分组和聚合查询
QueryWrapper支持分组查询和聚合函数:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 分组查询
wrapper.select("department", "COUNT(*) as count")
.groupBy("department")
.having("COUNT(*) > {0}", 5);
// SQL: SELECT department, COUNT(*) as count
// FROM user GROUP BY department HAVING COUNT(*) > 5
// 多字段分组
wrapper.select("department", "title", "AVG(salary) as avg_salary")
.groupBy("department", "title")
.orderByAsc("department", "title");
// SQL: SELECT department, title, AVG(salary) as avg_salary
// FROM user GROUP BY department, title ORDER BY department ASC, title ASC
四、排序和分页
4.1 排序操作
QueryWrapper提供了灵活的排序支持:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 单字段排序
wrapper.orderByAsc("create_time");
// SQL: ORDER BY create_time ASC
// 多字段排序
wrapper.orderByAsc("department").orderByDesc("salary");
// SQL: ORDER BY department ASC, salary DESC
// 条件排序
boolean needSort = true;
wrapper.orderBy(needSort, true, "name"); // 条件为true时排序
// SQL: ORDER BY name ASC
// 列表字段排序
List<String> sortFields = Arrays.asList("name", "age", "create_time");
wrapper.orderBy(true, false, sortFields); // 全部降序
// SQL: ORDER BY name DESC, age DESC, create_time DESC
4.2 分页查询
结合MyBatis-Plus的分页插件实现分页:
// 创建分页对象
Page<User> page = new Page<>(1, 10); // 当前页1,每页10条
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1)
.orderByDesc("create_time");
// 执行分页查询
IPage<User> userPage = userMapper.selectPage(page, wrapper);
// 获取分页信息
long total = userPage.getTotal(); // 总记录数
long pages = userPage.getPages(); // 总页数
List<User> records = userPage.getRecords(); // 当前页数据
五、高级特性详解
5.1 Lambda表达式支持
LambdaQueryWrapper提供了类型安全的查询方式:
// 创建LambdaQueryWrapper
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
// 类型安全的字段引用
lambdaWrapper.eq(User::getName, "张三")
.gt(User::getAge, 18)
.like(User::getEmail, "@example.com");
// SQL: WHERE name = '张三' AND age > 18 AND email LIKE '%@example.com%'
// 避免字段名拼写错误
// 传统方式:wrapper.eq("namme", "张三") // 拼写错误,运行时才会发现
// Lambda方式:lambdaWrapper.eq(User::getNamme, "张三") // 编译时就会报错
5.2 自定义SQL片段
对于复杂的SQL需求,可以使用apply方法嵌入自定义SQL:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 使用apply添加自定义SQL
wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2023-08-01")
.apply("salary * 1.1 > {0}", 10000);
// SQL: WHERE date_format(create_time,'%Y-%m-%d') = '2023-08-01'
// AND salary * 1.1 > 10000
// 使用last方法在SQL末尾添加内容
wrapper.last("FOR UPDATE");
// SQL: WHERE ... FOR UPDATE
5.3 子查询和EXISTS
QueryWrapper支持子查询和EXISTS查询:
QueryWrapper<User> wrapper = new QueryWrapper<>();
// EXISTS子查询
wrapper.exists("SELECT 1 FROM user_role ur WHERE ur.user_id = id AND ur.role_id = {0}", 1);
// SQL: WHERE EXISTS (SELECT 1 FROM user_role ur WHERE ur.user_id = id AND ur.role_id = 1)
// NOT EXISTS子查询
wrapper.notExists("SELECT 1 FROM blacklist b WHERE b.user_id = id");
// SQL: WHERE NOT EXISTS (SELECT 1 FROM blacklist b WHERE b.user_id = id)
// IN子查询
wrapper.inSql("department_id", "SELECT id FROM department WHERE type = '技术'");
// SQL: WHERE department_id IN (SELECT id FROM department WHERE type = '技术')
六、性能优化和最佳实践
6.1 索引优化建议
合理使用QueryWrapper可以充分利用数据库索引:
| 查询类型 | 索引友好度 | 优化建议 |
|---|---|---|
| eq操作 | ⭐⭐⭐⭐⭐ | 最适合索引,优先使用 |
| range操作(gt/lt/between) | ⭐⭐⭐⭐ | 适合范围索引 |
| like操作 | ⭐⭐ | 只有前缀匹配能使用索引 |
| or操作 | ⭐ | 可能导致索引失效,慎用 |
| 函数操作 | ⭐ | 索引通常失效 |
6.2 避免N+1查询问题
// 错误的做法:多次查询
List<Department> departments = departmentMapper.selectList(null);
for (Department dept : departments) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("department_id", dept.getId());
List<User> users = userMapper.selectList(wrapper); // N+1查询
dept.setUsers(users);
}
// 正确的做法:一次查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.in("department_id", departments.stream().map(Department::getId).collect(Collectors.toList()));
List<User> allUsers = userMapper.selectList(wrapper);
// 内存中分组处理
Map<Long, List<User>> usersByDept = allUsers.stream()
.collect(Collectors.groupingBy(User::getDepartmentId));
6.3 条件重用和构建器模式
对于复杂的查询条件,可以使用构建器模式:
public class UserQueryBuilder {
private final QueryWrapper<User> wrapper = new QueryWrapper<>();
public UserQueryBuilder nameLike(String name) {
if (StringUtils.isNotBlank(name)) {
wrapper.like("name", name);
}
return this;
}
public UserQueryBuilder ageBetween(Integer minAge, Integer maxAge) {
if (minAge != null && maxAge != null) {
wrapper.between("age", minAge, maxAge);
}
return this;
}
public UserQueryBuilder inDepartment(List<Long> departmentIds) {
if (CollectionUtils.isNotEmpty(departmentIds)) {
wrapper.in("department_id", departmentIds);
}
return this;
}
public QueryWrapper<User> build() {
return wrapper;
}
}
// 使用示例
QueryWrapper<User> wrapper = new UserQueryBuilder()
.nameLike("张")
.ageBetween(20, 30)
.inDepartment(Arrays.asList(1L, 2L, 3L))
.build();
七、实战案例:复杂业务查询
7.1 电商用户查询案例
public Page<UserVO> searchUsers(UserSearchDTO searchDTO) {
Page<User> page = new Page<>(searchDTO.getPageNum(), searchDTO.getPageSize());
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 基础条件
if (StringUtils.isNotBlank(searchDTO.getKeyword())) {
wrapper.and(w -> w.like("username", searchDTO.getKeyword())
.or()
.like("email", searchDTO.getKeyword())
.or()
.like("phone", searchDTO.getKeyword()));
}
// 状态过滤
if (CollectionUtils.isNotEmpty(searchDTO.getStatusList())) {
wrapper.in("status", searchDTO.getStatusList());
}
// 时间范围
if (searchDTO.getStartTime() != null) {
wrapper.ge("create_time", searchDTO.getStartTime());
}
if (searchDTO.getEndTime() != null) {
wrapper.le("create_time", searchDTO.getEndTime());
}
// 会员等级范围
if (searchDTO.getMinLevel() != null && searchDTO.getMaxLevel() != null) {
wrapper.between("vip_level", searchDTO.getMinLevel(), searchDTO.getMaxLevel());
}
// 排序处理
if (StringUtils.isNotBlank(searchDTO.getSortField())) {
if ("desc".equalsIgnoreCase(searchDTO.getSortOrder())) {
wrapper.orderByDesc(searchDTO.getSortField());
} else {
wrapper.orderByAsc(searchDTO.getSortField());
}
} else {
wrapper.orderByDesc("create_time"); // 默认排序
}
// 执行查询
IPage<User> userPage = userMapper.selectPage(page, wrapper);
// 转换为VO
return convertToVOPage(userPage);
}
7.2 报表统计查询案例
public List<DepartmentStatsVO> getDepartmentStatistics(StatsQuery query) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 选择统计字段
wrapper.select("department_id",
"COUNT(*) as total_count",
"SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as active_count",
"AVG(salary) as avg_salary",
"MAX(create_time) as last_join_time");
// 时间条件
if (query.getStartDate() != null) {
wrapper.ge("create_time", query.getStartDate());
}
if (query.getEndDate() != null) {
wrapper.le("create_time", query.getEndDate());
}
// 分组条件
wrapper.groupBy("department_id")
.having("COUNT(*) > {0}", 0); // 只统计有员工的部门
// 排序
wrapper.orderByDesc("total_count");
// 执行查询
List<Map<String, Object>> stats = userMapper.selectMaps(wrapper);
// 转换为VO
return stats.stream()
.map(this::convertToStatsVO)
.collect(Collectors.toList());
}
八、常见问题与解决方案
8.1 SQL注入防护
QueryWrapper默认提供了SQL注入防护,但需要注意:
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



