1. MyBatis-Plus复杂查询实战:Lambda与Wrapper的深度结合
在Java持久层开发中,MyBatis-Plus作为MyBatis的增强工具,其Lambda与Wrapper组合查询方式已经成为处理复杂条件的行业标准实践。我经历过从早期XML配置到注解开发,再到如今Lambda表达式的演进过程,这种写法不仅让代码更简洁,更重要的是大幅提升了可维护性——当查询条件需要变更时,你不再需要来回比对字段名,IDE的智能提示会帮你避免90%的手误。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心组件解析:理解Wrapper体系
2.1 Wrapper家族图谱
MyBatis-Plus提供了完整的Wrapper体系:
QueryWrapper:基础查询条件构造器LambdaQueryWrapper:基于Lambda的防误写版本AbstractLambdaWrapper:所有Lambda包装器的抽象父类UpdateWrapper:更新条件构造器LambdaUpdateWrapper:Lambda风格的更新构造器
实际项目中,我强烈推荐始终使用Lambda变体。上周刚有个同事因为手写字段名user_name写成usre_name导致生产环境查询异常,这种低级错误用Lambda完全可以避免。
2.2 Lambda表达式的魔法
当你在Wrapper中使用User::getName这样的语法时,MyBatis-Plus底层通过解析Lambda的元信息获取实体属性名。这个过程不依赖字符串,因此具有编译期检查的优势。来看个典型示例:
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getDepartmentId, 5)
.ge(User::getCreateTime, LocalDate.now().minusMonths(1))
.isNotNull(User::getEmail);
关键技巧:在IntelliJ IDEA中,可以通过
Ctrl+P查看Lambda方法引用的参数提示,确保选择正确的getter方法。
3. 多条件组合查询实战
3.1 基础条件构建
构建复杂查询时,条件的组合方式直接影响SQL性能。以下是经过生产验证的最佳实践:
java复制public Page<User> searchUsers(UserQuery query) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 精确匹配条件
if (StringUtils.isNotBlank(query.getUsername())) {
wrapper.eq(User::getUsername, query.getUsername());
}
// 范围查询
if (query.getMinAge() != null) {
wrapper.ge(User::getAge, query.getMinAge());
}
if (query.getMaxAge() != null) {
wrapper.le(User::getAge, query.getMaxAge());
}
// 模糊查询
if (StringUtils.isNotBlank(query.getKeyword())) {
wrapper.and(w -> w.like(User::getName, query.getKeyword())
.or()
.like(User::getDescription, query.getKeyword()));
}
// 排序逻辑
wrapper.orderByDesc(User::getCreateTime);
return userMapper.selectPage(new Page<>(query.getPage(), query.getSize()), wrapper);
}
3.2 嵌套条件处理
遇到需要括号优先级的复杂逻辑时,nested方法比直接拼接and/or更清晰:
java复制wrapper.nested(w -> w.eq(User::getType, 1).or().eq(User::getType, 2))
.gt(User::getScore, 60);
对应的SQL是:WHERE (type = 1 OR type = 2) AND score > 60
4. 动态SQL构建技巧
4.1 条件过滤的优雅写法
避免在Service层堆积大量if语句,可以封装条件判断逻辑:
java复制wrapper.gt(ObjectUtils.isNotEmpty(query.getMinSalary()), User::getSalary, query.getMinSalary())
.lt(ObjectUtils.isNotEmpty(query.getMaxSalary()), User::getSalary, query.getMaxSalary());
4.2 动态表名处理
在多租户系统中,可能需要动态切换表名:
java复制String dynamicTableName = "user_" + tenantId;
wrapper.setEntityClass(User.class); // 必须设置实体类型
wrapper.getCustomSqlSegment(); // 触发SQL构建
wrapper.getSqlSet(); // 获取SET子句
5. 性能优化要点
5.1 索引命中规则
- 避免在索引列上使用函数操作:
wrapper.apply("DATE(create_time) = {0}", date) - 范围查询放最后:多个
eq条件应放在between/ge/le之前 - 模糊查询优化:
likeRight比like更高效(使用索引)
5.2 查询字段控制
默认查询所有字段会导致性能浪费:
java复制wrapper.select(User::getId, User::getName, User::getAvatar);
对于大字段文本,建议单独查询:
java复制wrapper.select(User.class, info ->
!info.getColumn().equals("content") &&
!info.getColumn().equals("long_text"));
6. 复杂查询案例集锦
6.1 分组统计查询
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("department_id", "count(*) as user_count")
.groupBy("department_id")
.having("user_count > 10");
6.2 联表查询方案
虽然MyBatis-Plus主要处理单表,但可以通过apply实现简单联表:
java复制wrapper.apply("EXISTS (SELECT 1 FROM user_role ur WHERE ur.user_id = id AND ur.role_id = {0})", roleId);
对于复杂联表,建议仍然使用XML或注解方式。
7. 常见问题排查
7.1 NPE问题定位
当遇到NullPointerException时,通常是因为:
- 忘记初始化Wrapper:
LambdaQueryWrapper<User> wrapper = null; - 实体类没有对应的getter方法
- Lambda引用了静态字段而非实例方法
7.2 SQL语法错误
查看最终生成的SQL有助于调试:
java复制System.out.println(wrapper.getCustomSqlSegment());
生产环境记得移除该调试代码,建议使用日志框架的debug级别输出。
8. 扩展应用场景
8.1 动态租户过滤
在多租户系统中,可以统一添加租户条件:
java复制public <T> LambdaQueryWrapper<T> addTenantCondition(LambdaQueryWrapper<T> wrapper) {
return wrapper.eq(getTenantColumn(entityClass), getCurrentTenantId());
}
8.2 逻辑删除整合
当启用逻辑删除时(@TableLogic),所有查询会自动加上删除条件。需要查询已删除数据时:
java复制wrapper.eq(User::getDeleted, 1); // 显式指定
9. 最佳实践总结
经过多个项目的实战检验,我总结出以下黄金法则:
- 类型安全优先:始终使用LambdaWrapper而非普通Wrapper
- 条件组合清晰:复杂逻辑使用
nested明确优先级 - SQL可读性:避免过长链式调用,适当换行和注释
- 性能意识:控制查询字段,注意索引使用
- 调试技巧:善用
getCustomSqlSegment()输出SQL
最后分享一个真实案例:在某次性能优化中,我们将一个包含15个条件的动态查询从XML迁移到LambdaWrapper,不仅代码量减少了40%,而且由于避免了字符串拼接错误,相关BUG报告直接归零。这充分证明了类型安全的Lambda查询在现代Java开发中的价值。
