一、核心概念

条件构造器(Wrapper) 是 MyBatis-Plus 的核心组件,通过链式调用方法构建 SQL 查询条件。主要分为两类:

  • QueryWrapper:基于字符串字段名
  • LambdaQueryWrapper:基于 Lambda 表达式(推荐,类型安全)

核心方法分类

类型 方法
比较条件 eq, ne, gt, ge, lt, le
模糊查询 like, likeLeft, likeRight, notLike
范围条件 between, notBetween, in, notIn
空值判断 isNull, isNotNull
逻辑连接 and, or, nested(嵌套条件)
排序分组 orderByAsc, orderByDesc, groupBy
字段控制 select(指定返回字段)

二、详细操作步骤(基于 LambdaQueryWrapper)

1. 初始化构造器

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

2. 比较条件方法

// 等于
wrapper.eq(User::getAge, 25);                 // age = 25

// 不等于
wrapper.ne(User::getName, "admin");           // name <> 'admin'

// 大于
wrapper.gt(User::getScore, 90);               // score > 90

// 大于等于
wrapper.ge(User::getCreateTime, "2023-01-01"); // create_time >= '2023-01-01'

// 小于
wrapper.lt(User::getLoginCount, 10);          // login_count < 10

// 小于等于
wrapper.le(User::getAge, 18);                 // age <= 18

3. 模糊查询

// 全模糊:%value%
wrapper.like(User::getName, "张");             // name LIKE '%张%'

// 左模糊:%value
wrapper.likeLeft(User::getEmail, "@gmail.com"); // email LIKE '%@gmail.com'

// 右模糊:value%
wrapper.likeRight(User::getPhone, "138");      // phone LIKE '138%'

// 不包含
wrapper.notLike(User::getTitle, "test");       // title NOT LIKE '%test%'

4. 范围条件

// 区间查询
wrapper.between(User::getAge, 20, 30);        // age BETWEEN 20 AND 30

// 排除区间
wrapper.notBetween(User::getSalary, 5000, 10000); 

// IN 查询
List<Integer> ids = Arrays.asList(101, 102, 105);
wrapper.in(User::getId, ids);                 // id IN (101,102,105)

// NOT IN
wrapper.notIn(User::getRole, "guest", "test"); // role NOT IN ('guest','test')

5. 空值判断

wrapper.isNull(User::getDeleteFlag);           // delete_flag IS NULL
wrapper.isNotNull(User::getUpdateTime);        // update_time IS NOT NULL

6. 逻辑连接

// AND 连接(默认)
wrapper.eq(User::getDeptId, 2).gt(User::getAge, 25);

// OR 连接
wrapper.eq(User::getStatus, 1)
       .or()
       .eq(User::getStatus, 2);               // status=1 OR status=2

// 嵌套条件(括号优先级)
wrapper.nested(wq -> wq.gt(User::getScore, 90).lt(User::getAge, 30))
       .eq(User::getType, "A");               // (score>90 AND age<30) AND type='A'

// 动态 AND 条件
wrapper.and(wq -> wq.eq(User::getActive, true).isNotNull(User::getEmail));

7. 排序与分组

// 单字段排序
wrapper.orderByAsc(User::getAge);              // ORDER BY age ASC
wrapper.orderByDesc(User::getCreateTime);      // ORDER BY create_time DESC

// 多字段排序
wrapper.orderByAsc(User::getDeptId, User::getAge); 

// 分组
wrapper.groupBy(User::getDeptId);              // GROUP BY dept_id

// 分组后筛选 (需配合 having)
wrapper.groupBy(User::getDeptId)
       .having("SUM(salary) > {0}", 100000);   // GROUP BY dept_id HAVING SUM(salary)>100000

8. 字段控制

// 指定返回字段
wrapper.select(User::getId, User::getName, User::getEmail);

// 排除敏感字段
wrapper.select(User.class, 
    info -> !"password".equals(info.getColumn()));

三、常见错误与解决

  1. NPE(空指针异常)

    // 错误:可能传入null值
    wrapper.eq(User::getName, inputName);
    
    // 正确:动态条件
    wrapper.eq(StringUtils.isNotBlank(inputName), User::getName, inputName);
    
  2. 条件覆盖问题

    // 错误:同名字段多次调用会覆盖
    wrapper.gt(User::getAge, 18).gt(User::getAge, 20); // 只保留 age>20
    
    // 正确:使用链式或逻辑组合
    wrapper.gt(User::getAge, 18).lt(User::getAge, 30); // 18 < age < 30
    
  3. 日期格式错误

    // 错误:直接传入字符串
    wrapper.ge(User::getCreateTime, "2023-01-01");
    
    // 正确:使用Date类型或指定格式
    wrapper.ge(User::getCreateTime, 
        DateUtil.parse("2023-01-01", "yyyy-MM-dd"));
    

四、关键注意事项

  1. SQL 注入防护
    禁止直接拼接用户输入:

    // ⛔ 危险写法
    wrapper.apply("date_format(create_time,'%Y') = " + userInput);
    
    // ✅ 安全写法
    wrapper.apply("date_format(create_time,'%Y') = {0}", userInput);
    
  2. 索引失效场景

    • 避免对索引列使用 !=NOT IN
    • 模糊查询优先使用 likeRight()(前缀匹配)
    • 避免在索引列上使用函数(如 YEAR(create_time)
  3. 性能陷阱

    // 低效:循环单条查询
    for (Long id : ids) {
        mapper.selectById(id);
    }
    
    // 高效:单次IN查询
    wrapper.in(!ids.isEmpty(), User::getId, ids);
    

五、高级使用技巧

1. 动态条件构建

public LambdaQueryWrapper<User> buildQuery(UserQuery query) {
    return new LambdaQueryWrapper<User>()
        .eq(query.getId() != null, User::getId, query.getId())
        .like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
        .between(query.getStart() != null && query.getEnd() != null, 
                 User::getCreateTime, query.getStart(), query.getEnd());
}

2. 条件复用

// 基础条件
LambdaQueryWrapper<User> baseWrapper = new LambdaQueryWrapper<User>()
    .eq(User::getActive, true);

// 扩展条件
LambdaQueryWrapper<User> extendWrapper = baseWrapper.clone()
    .gt(User::getScore, 90)
    .orderByDesc(User::getCreateTime);

3. 子查询处理

// 子查询:dept_id 在部门表的有效ID中
wrapper.inSql(User::getDeptId, 
    "SELECT id FROM dept WHERE status = 1");

六、最佳实践与性能优化

  1. 索引优化原则

    • 等值查询(eq)放在范围查询(gt/lt)前
    • 高区分度字段(如ID)优先作为条件
  2. 大表查询策略

    // 分页必加排序
    Page<User> page = new Page<>(1, 10)
        .addOrder(OrderItem.desc("create_time"));
    
    // 避免 SELECT * 
    wrapper.select(User::getId, User::getName);
    
  3. 批量操作优化

    • 单次 IN 查询限制 1000 条以内
    • 超量数据分批查询或使用游标
  4. 关联查询建议

    • 简单关联用 JOIN + apply()
    • 复杂关联使用 XML/注解 SQL

七、完整示例

// 查询技术部25-35岁员工,按薪资降序
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getName, User::getSalary)
       .eq(User::getDeptName, "技术部")
       .between(User::getAge, 25, 35)
       .isNotNull(User::getEmail)
       .orderByDesc(User::getSalary)
       .last("LIMIT 100");

List<User> users = userMapper.selectList(wrapper);

总结:核心要点速查

场景 推荐方法 避坑指南
等值查询 eq() 注意空值处理
范围查询 between() 避免开区间导致索引失效
模糊匹配 likeRight() 优先右模糊保证索引命中
动态条件 eq(boolean condition, ...) 替代 if 嵌套
大数量 IN 查询 分批处理 单次不超过 1000 条
排序分页 orderBy().last("LIMIT") 必须显式排序
敏感字段 select() 指定字段 避免返回 password 等敏感信息

掌握这些条件拼接方法,可显著提升 MyBatis-Plus 开发效率与代码质量。建议结合 Lambda 表达式使用,兼顾类型安全和编码效率。