MyBatis-Plus条件构造器QueryWrapper深度指南:告别手写SQL的烦恼

MyBatis-Plus条件构造器QueryWrapper深度指南:告别手写SQL的烦恼

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

引言:为什么需要QueryWrapper?

在日常Java开发中,SQL编写一直是开发者面临的痛点之一。传统的MyBatis虽然提供了强大的SQL映射能力,但仍然需要手动编写大量SQL语句,这不仅容易出错,还难以维护。MyBatis-Plus的QueryWrapper条件构造器正是为了解决这一痛点而生,它通过链式API让开发者能够以面向对象的方式构建复杂的查询条件,彻底告别手写SQL的烦恼。

通过本文,你将掌握:

  • QueryWrapper的核心概念和设计哲学
  • 各种条件方法的详细用法和最佳实践
  • 高级嵌套查询和复杂条件构建技巧
  • 性能优化和常见陷阱避免
  • 实际项目中的综合应用案例

一、QueryWrapper核心架构解析

1.1 类层次结构

QueryWrapper继承体系采用了经典的模板方法模式,其核心类关系如下:

mermaid

1.2 核心设计理念

QueryWrapper的设计遵循以下几个重要原则:

  1. 链式调用:所有方法都返回this,支持流畅的API调用
  2. 条件参数化:自动处理SQL参数绑定,防止SQL注入
  3. 类型安全:通过泛型确保类型一致性
  4. 可组合性:支持复杂的嵌套条件组合

二、基础条件方法详解

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注入防护,但需要注意:

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值