QueryWrapper 是 MyBatis-Plus 提供的核心条件构造器(Wrapper),用于构建复杂的 SQL 查询条件,无需手写 SQL 即可实现 WHEREANDORGROUP BYHAVINGORDER BY 等功能。它极大提升了开发效率,是 MyBatis-Plus 最常用、最强大的功能之一。


一、核心概念

1. 什么是 QueryWrapper

  • QueryWrapper<T> 是 MyBatis-Plus 提供的泛型条件构造器,用于构建 SELECT 查询的 WHERE 条件。
  • 它通过链式调用方式,将 Java 方法调用自动转换为 SQL 条件片段。
  • 支持:
    • 字段比较(eq, ne, gt, ge, lt, le
    • 逻辑运算(and, or
    • 模糊查询(like, notLike
    • 范围查询(between, notBetween
    • 空值判断(isNull, isNotNull
    • 分组与聚合(groupBy, having
    • 排序(orderByAsc, orderByDesc
    • 自定义 SQL 片段(apply
    • 子查询支持

2. 相关 Wrapper 类

类名 用途
QueryWrapper<T> 查询条件构造器(最常用)
UpdateWrapper<T> 更新条件构造器
LambdaQueryWrapper<T> Lambda 版本,类型安全,避免字段名硬编码
QueryChainWrapper<T> 链式查询调用(如 lambda().eq(...).list()

二、操作步骤(非常详细)

步骤 1:准备实体类与 Mapper

@Data
@TableName("user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private LocalDateTime createTime;
    private Integer status;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {}

步骤 2:注入 Mapper 并使用 QueryWrapper

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

步骤 3:基础查询(等于、不等于、范围)

示例 1:查询年龄等于 25 的用户

public List<User> getUsersByAge(Integer age) {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("age", age); // WHERE age = ?
    return userMapper.selectList(wrapper);
}

示例 2:查询年龄在 18 到 30 之间的用户

public List<User> getUsersByAgeRange() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age", 18, 30); // WHERE age BETWEEN ? AND ?
    return userMapper.selectList(wrapper);
}

示例 3:查询姓名不为空且状态为 1 的用户

public List<User> getValidUsers() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.isNotNull("name")     // WHERE name IS NOT NULL
           .eq("status", 1);      // AND status = 1
    return userMapper.selectList(wrapper);
}

步骤 4:模糊查询

示例 4:姓名包含“张”且邮箱以 “@example.com” 结尾

public List<User> getUsersByNameAndEmail() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.like("name", "张")             // WHERE name LIKE '%张%'
           .likeRight("email", "@example.com"); // AND email LIKE '@example.com%' (左匹配)
    return userMapper.selectList(wrapper);
}

🔍 likeLeft:右模糊(%value
likeRight:左模糊(value%


步骤 5:逻辑运算(AND / OR)

示例 5:复杂逻辑 (age > 20 AND status = 1) OR name LIKE '王%'

public List<User> getComplexUsers() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.nested(qw -> qw.gt("age", 20).eq("status", 1)) // (age > 20 AND status = 1)
           .or()
           .likeLeft("name", "王"); // OR name LIKE '王%'
    return userMapper.selectList(wrapper);
}

nested(...) 用于创建括号组。


步骤 6:分组与聚合

示例 6:按状态分组,统计每组人数,且人数 > 2

public List<Map<String, Object>> getUserCountByStatus() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.select("status", "count(*) as count") // SELECT status, count(*)
           .groupBy("status")                    // GROUP BY status
           .having("count(*) > {0}", 2);          // HAVING count(*) > 2
    return userMapper.selectMaps(wrapper);
}

⚠️ 使用 selectMaps 获取 Map 结果。


步骤 7:排序

示例 7:按年龄降序,创建时间升序

public List<User> getUsersSorted() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByDesc("age")       // ORDER BY age DESC
           .orderByAsc("create_time"); // , create_time ASC
    return userMapper.selectList(wrapper);
}

步骤 8:使用 Lambda 版本(推荐)

避免字段名硬编码,类型安全。

public List<User> getUsersByAgeLambda(Integer age) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getAge, age)           // 字段引用
           .like(User::getName, "张")
           .orderByDesc(User::getCreateTime);
    return userMapper.selectList(wrapper);
}

强烈推荐使用 LambdaQueryWrapper,防止字段名拼写错误。


步骤 9:自定义 SQL 片段(apply

示例 9:使用数据库函数

wrapper.apply("date_format(create_time, '%Y-%m') = {0}", "2025-08");

生成 SQL:

WHERE date_format(create_time, '%Y-%m') = '2025-08'

⚠️ apply 不做 SQL 注入检查,慎用用户输入!


三、常见错误与解决方案

错误现象 原因 解决方案
查询无结果或条件未生效 字段名写错(如 user_name 写成 userName 使用 LambdaQueryWrapper
报错 Unknown column 数据库字段与实体属性不一致,且未用 @TableField 映射 添加 @TableField("db_column_name")
OR 逻辑混乱 未使用 nested 分组 nested 包裹 AND 条件
分页失效 未配合 Page 对象使用 使用 selectPage(page, wrapper)
apply 导致 SQL 注入 拼接用户输入 使用 {0} 占位符传参

四、注意事项

  1. 字段名大小写敏感性
    MySQL 默认不区分,但建议保持一致。使用 @TableField 明确映射。

  2. null 值处理
    eq("name", null) 生成 name = NULL,应使用 isNull("name")

  3. 避免在 Wrapper 中拼接 SQL
    优先使用内置方法,apply 仅用于特殊场景。

  4. 分页查询需用 Page

    Page<User> page = new Page<>(1, 10);
    userMapper.selectPage(page, wrapper);
    
  5. Wrapper 不支持 JOIN
    多表关联需使用 @Select 注解或 XML。


五、使用技巧

技巧 1:条件动态拼接(空值自动忽略)

wrapper.eq(age != null, "age", age)
       .like(StringUtils.hasText(name), "name", name);

✅ 第一个参数为 booleantrue 时才添加条件。

技巧 2:链式调用(QueryChainWrapper)

@Autowired
private UserMapper userMapper;

// 启用 chain 模式
public List<User> getUsersChained() {
    return userMapper.selectList(
        new QueryWrapper<User>()
            .eq("status", 1)
            .orderByDesc("create_time")
    );
}

// 或使用 lambdaChain
public List<User> getUsersLambdaChain() {
    return new LambdaQueryChainWrapper<>(userMapper)
        .eq(User::getStatus, 1)
        .like(User::getName, "张")
        .list();
}

技巧 3:子查询支持

wrapper.in("id", 
    new QueryWrapper<User>().select("id").eq("status", 0).getSqlSegment()
);

六、最佳实践

实践 说明
✅ 优先使用 LambdaQueryWrapper 类型安全,避免硬编码
✅ 动态条件使用 condition 参数 .eq(age != null, "age", age)
✅ 复杂查询拆分逻辑 避免单个 Wrapper 过于庞大
✅ 分页使用 IPage 结合 PageHelper 或 MP 自带分页
✅ 日志开启 SQL 输出 便于调试生成的 SQL
✅ 避免 SELECT * 使用 select() 指定字段

七、性能优化

优化点 说明
索引优化 确保 WHEREORDER BY 字段有索引
避免全表扫描 LIKE '%value%' 无法使用索引,尽量用 LIKE 'value%'
减少字段查询 select("id", "name") 替代 SELECT *
合理分页 避免 OFFSET 过大,可用 WHERE id > lastId 实现游标分页
缓存结果 频繁查询可结合 Redis 缓存
SQL 复用 相同条件可复用 Wrapper 实例(注意线程安全)

八、总结

项目 内容
核心类 QueryWrapper<T>, LambdaQueryWrapper<T>
核心优势 链式调用、类型安全、免 SQL
推荐用法 LambdaQueryWrapper + condition 动态拼接
适用场景 单表复杂查询、动态条件
性能关键 索引、避免全表扫描、合理分页

一句话总结
QueryWrapper(尤其是 LambdaQueryWrapper)是 MyBatis-Plus 实现高效、安全、可维护查询的核心工具,掌握其用法是使用 MP 的必备技能。