一、核心概念
条件构造器(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()));
三、常见错误与解决
NPE(空指针异常)
// 错误:可能传入null值 wrapper.eq(User::getName, inputName); // 正确:动态条件 wrapper.eq(StringUtils.isNotBlank(inputName), User::getName, inputName);
条件覆盖问题
// 错误:同名字段多次调用会覆盖 wrapper.gt(User::getAge, 18).gt(User::getAge, 20); // 只保留 age>20 // 正确:使用链式或逻辑组合 wrapper.gt(User::getAge, 18).lt(User::getAge, 30); // 18 < age < 30
日期格式错误
// 错误:直接传入字符串 wrapper.ge(User::getCreateTime, "2023-01-01"); // 正确:使用Date类型或指定格式 wrapper.ge(User::getCreateTime, DateUtil.parse("2023-01-01", "yyyy-MM-dd"));
四、关键注意事项
SQL 注入防护
禁止直接拼接用户输入:// ⛔ 危险写法 wrapper.apply("date_format(create_time,'%Y') = " + userInput); // ✅ 安全写法 wrapper.apply("date_format(create_time,'%Y') = {0}", userInput);
索引失效场景
- 避免对索引列使用
!=
、NOT IN
- 模糊查询优先使用
likeRight()
(前缀匹配) - 避免在索引列上使用函数(如
YEAR(create_time)
)
- 避免对索引列使用
性能陷阱
// 低效:循环单条查询 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");
六、最佳实践与性能优化
索引优化原则
- 等值查询(
eq
)放在范围查询(gt/lt
)前 - 高区分度字段(如ID)优先作为条件
- 等值查询(
大表查询策略
// 分页必加排序 Page<User> page = new Page<>(1, 10) .addOrder(OrderItem.desc("create_time")); // 避免 SELECT * wrapper.select(User::getId, User::getName);
批量操作优化
- 单次
IN
查询限制 1000 条以内 - 超量数据分批查询或使用游标
- 单次
关联查询建议
- 简单关联用
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 表达式使用,兼顾类型安全和编码效率。