1. MyBatis-Plus复杂查询实战指南
作为Java开发者,我们每天都要和各种数据库查询打交道。MyBatis-Plus作为MyBatis的增强工具,提供了Wrapper和Lambda表达式这两种强大的查询构造方式,让复杂查询变得简单优雅。今天我就结合自己多年项目经验,详细解析如何在实际开发中高效使用这两种方式构建多条件查询。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境准备
2.1 依赖配置
首先确保你的项目已经正确引入MyBatis-Plus依赖。对于Maven项目,在pom.xml中添加:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
注意:版本号请根据实际情况选择,建议使用最新稳定版
2.2 实体类定义
假设我们有一个用户实体类User:
java复制@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private Integer age;
private String email;
private Integer status;
@TableField("create_time")
private Date createTime;
}
3. Wrapper基础查询
3.1 QueryWrapper基本使用
QueryWrapper是最基础的查询构造器,适合简单的条件查询:
java复制QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1)
.ge("age", 18)
.like("username", "张")
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
这段代码会生成类似这样的SQL:
sql复制SELECT * FROM sys_user
WHERE status = 1
AND age >= 18
AND username LIKE '%张%'
ORDER BY create_time DESC
3.2 常用条件方法
Wrapper提供了丰富的条件构造方法:
| 方法名 | 说明 | SQL等价 |
|---|---|---|
| eq | 等于 | = |
| ne | 不等于 | <> |
| gt | 大于 | > |
| ge | 大于等于 | >= |
| lt | 小于 | < |
| le | 小于等于 | <= |
| between | BETWEEN值1 AND值2 | BETWEEN...AND... |
| notBetween | NOT BETWEEN值1 AND值2 | NOT BETWEEN...AND... |
| like | LIKE '%值%' | LIKE '%值%' |
| notLike | NOT LIKE '%值%' | NOT LIKE '%值%' |
| likeLeft | LIKE '%值' | LIKE '%值' |
| likeRight | LIKE '值%' | LIKE '值%' |
| isNull | 字段IS NULL | IS NULL |
| isNotNull | 字段IS NOT NULL | IS NOT NULL |
| in | 字段IN (v0, v1, ...) | IN (v0, v1, ...) |
| notIn | 字段NOT IN (v0, v1, ...) | NOT IN (v0, v1, ...) |
| groupBy | 分组:GROUP BY字段 | GROUP BY |
| orderByAsc | 排序:ORDER BY字段ASC | ORDER BY...ASC |
| orderByDesc | 排序:ORDER BY字段DESC | ORDER BY...DESC |
4. Lambda表达式查询
4.1 LambdaQueryWrapper优势
LambdaQueryWrapper通过方法引用来指定字段,避免了字符串硬编码,提供了编译期检查:
java复制LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getStatus, 1)
.ge(User::getAge, 18)
.like(User::getUsername, "张")
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(lambdaWrapper);
4.2 类型安全的好处
使用Lambda表达式最大的好处是类型安全:
- 字段名修改时,编译器会报错
- IDE可以提供代码补全
- 重构时字段引用会自动更新
5. 复杂条件组合
5.1 逻辑组合查询
实际业务中经常需要组合多个条件:
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.and(w -> w.gt(User::getAge, 18).or().lt(User::getAge, 60))
.likeRight(User::getUsername, "张")
.between(User::getCreateTime, startDate, endDate);
生成的SQL类似:
sql复制SELECT * FROM sys_user
WHERE status = 1
AND (age > 18 OR age < 60)
AND username LIKE '张%'
AND create_time BETWEEN ? AND ?
5.2 嵌套查询示例
更复杂的嵌套条件:
java复制wrapper.nested(w -> w.eq(User::getType, 1).or().eq(User::getType, 2))
.and(w -> w.gt(User::getScore, 60).or().isNotNull(User::getVipFlag)));
对应SQL:
sql复制WHERE (type = 1 OR type = 2)
AND (score > 60 OR vip_flag IS NOT NULL)
6. 动态条件构建
6.1 条件判空处理
实际开发中经常需要根据参数是否为空来动态构建条件:
java复制public List<User> queryUsers(String username, Integer minAge, Integer maxAge, Integer status) {
return lambdaQuery()
.eq(status != null, User::getStatus, status)
.like(StringUtils.isNotBlank(username), User::getUsername, username)
.ge(minAge != null, User::getAge, minAge)
.le(maxAge != null, User::getAge, maxAge)
.list();
}
6.2 条件优先级处理
对于复杂的业务规则,可以这样处理:
java复制LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (isVipUser) {
wrapper.eq(User::getVipFlag, 1)
.gt(User::getScore, 80);
} else {
wrapper.eq(User::getStatus, 1)
.between(User::getAge, 18, 60);
}
7. 特殊场景处理
7.1 子查询处理
使用inSql或exists方法实现子查询:
java复制// 查询分数高于平均分的用户
wrapper.inSql(User::getId, "SELECT user_id FROM user_score WHERE score > (SELECT AVG(score) FROM user_score)");
// 或者使用exists
wrapper.exists("SELECT 1 FROM user_role WHERE user_role.user_id = sys_user.id AND role_id = 1");
7.2 自定义SQL片段
对于特别复杂的查询,可以结合@Select注解使用:
java复制@Select("SELECT * FROM sys_user ${ew.customSqlSegment}")
List<User> selectAll(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
8. 性能优化建议
8.1 索引命中原则
构建查询条件时要注意:
- 最左前缀原则:优先使用联合索引的最左列
- 避免在索引列上使用函数或计算
- 范围查询放在最后
8.2 查询字段控制
避免select *,只查询需要的字段:
java复制wrapper.select(User::getId, User::getUsername, User::getEmail);
9. 常见问题排查
9.1 条件不生效问题
如果发现某个条件没生效,检查:
- 参数是否为null(使用了条件方法的重载版本)
- 字段名是否正确(Lambda表达式可以避免这个问题)
- 是否有其他条件覆盖了当前条件
9.2 排序失效问题
排序失效常见原因:
- 字段名写错
- 排序字段值为null
- 多字段排序时顺序不对
10. 最佳实践总结
经过多个项目实践,我总结了以下经验:
- 优先使用Lambda表达式,避免字段名硬编码
- 复杂条件拆分成多个方法,提高可读性
- 动态条件使用条件方法的重载版本
- 性能敏感场景控制查询字段
- 多表关联查询考虑使用@Select注解自定义SQL
最后分享一个实用技巧:对于特别复杂的动态查询,可以封装一个QueryBuilder类,将条件构建逻辑集中管理,使Service层代码更加简洁。
