1. MyBatis-Plus条件构造器核心解析
作为MyBatis的增强工具,MyBatis-Plus的条件构造器是其最核心的功能模块之一。我在实际项目开发中发现,合理运用条件构造器能够减少约60%的SQL编写工作量。不同于传统的XML配置方式,它通过链式API实现了类型安全的查询条件构建。
条件构造器主要分为两种实现:
- QueryWrapper:用于SELECT查询条件构建
- UpdateWrapper:用于UPDATE操作条件构建
最新版本的MyBatis-Plus 3.x对条件构造器进行了多项优化,包括:
- Lambda表达式支持
- 嵌套条件查询
- 子查询支持
- 防SQL注入机制增强
2. 基础用法与链式调用
2.1 创建Wrapper实例
java复制// 查询条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 更新条件构造器
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
建议在实际项目中通过Wrappers.<T>query()和Wrappers.<T>update()静态工厂方法创建,这样可以获得更好的类型推断支持。
2.2 常用条件方法
基础比较操作:
java复制queryWrapper.eq("name", "张三") // name = '张三'
.ne("age", 20) // age != 20
.gt("score", 90) // score > 90
.ge("create_time", LocalDate.now()) // >= 当天
.lt("update_time", LocalDateTime.now()) // < 当前时间
.le("status", 1); // status <= 1
注意:所有条件方法都支持多参数重载,可以指定是否忽略null值
3. 高级查询功能实现
3.1 模糊查询与匹配
java复制// 左模糊
wrapper.likeLeft("name", "张") // name LIKE '%张'
// 右模糊
wrapper.likeRight("mobile", "138") // mobile LIKE '138%'
// 全模糊
wrapper.like("address", "北京") // address LIKE '%北京%'
// NOT LIKE
wrapper.notLike("email", "@test.com")
// 正则匹配
wrapper.apply("regexp_like(name, {0})", "^张.*")
3.2 范围查询
java复制// BETWEEN
wrapper.between("age", 18, 30)
// NOT BETWEEN
wrapper.notBetween("salary", 5000, 10000)
// IN查询
wrapper.in("dept_id", Arrays.asList(1, 2, 3))
// NOT IN
wrapper.notIn("role_id", Collections.singletonList(0))
4. 嵌套查询与子查询
4.1 复杂条件组合
java复制wrapper.and(qw -> qw.gt("score", 90).eq("status", 1))
.or(qw -> qw.lt("age", 18).isNotNull("parent_id"))
对应的SQL:
sql复制WHERE (score > 90 AND status = 1)
OR (age < 18 AND parent_id IS NOT NULL)
4.2 子查询支持
java复制// EXISTS子查询
wrapper.exists("SELECT 1 FROM user_role ur WHERE ur.user_id = id")
// IN子查询
wrapper.inSql("role_id", "SELECT id FROM role WHERE status = 1")
// 作为查询字段
wrapper.select("id", "name")
.select("(SELECT COUNT(*) FROM order WHERE user_id = id) AS order_count")
5. Lambda表达式写法
5.1 LambdaWrapper优势
类型安全的属性引用:
java复制LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
lambdaWrapper.eq(User::getName, "张三")
.gt(User::getAge, 18)
.orderByAsc(User::getCreateTime);
避免硬编码字段名的好处:
- 编译时检查
- IDE智能提示
- 重构友好
- 避免拼写错误
5.2 复杂Lambda示例
java复制wrapper.nested(w -> w.eq(User::getType, 1).or().eq(User::getType, 2))
.and(w -> w.gt(User::getScore, 60).lt(User::getScore, 90))
.apply("date_format(create_time,'%Y-%m') = {0}", "2023-08")
6. 实际应用中的性能优化
6.1 索引命中原则
java复制// 好的写法(符合最左前缀)
wrapper.eq("dept_id", 1)
.likeRight("employee_no", "2023")
.orderByAsc("create_time");
// 应避免的写法
wrapper.or().eq("status", 1) // OR可能导致索引失效
.isNotNull("update_time") // 非前置条件
6.2 大数据量查询优化
java复制// 分页查询优化
wrapper.select("id", "name") // 只查必要字段
.eq("status", 1)
.last("LIMIT 10000, 10"); // 明确分页
// 使用游标查询
try (Cursor<User> cursor = userMapper.selectCursor(wrapper)) {
cursor.forEach(user -> {
// 处理逻辑
});
}
7. 常见问题排查
7.1 参数处理异常
java复制// 错误示例(直接拼接值)
wrapper.apply("date_format(create_time,'%Y-%m') = "+ dateStr)
// 正确做法(预编译)
wrapper.apply("date_format(create_time,'%Y-%m') = {0}", dateStr)
7.2 NPE问题防护
java复制// 安全写法(忽略null值)
wrapper.eq(StringUtils.isNotBlank(name), "name", name)
.ge(age != null, "age", age)
// 或者使用条件过滤器
wrapper.setEntity(user) // 自动忽略null字段
.eq(Objects.nonNull(user.getId()), "id", user.getId())
8. 最佳实践建议
- Wrapper复用:对于常用查询条件,建议封装为静态方法
java复制public class WrapperFactory {
public static QueryWrapper<User> activeUsers() {
return Wrappers.<User>query()
.eq("status", 1)
.eq("is_deleted", 0);
}
}
- 动态SQL构建:复杂场景结合
@SelectProvider使用
java复制@SelectProvider(type = UserSqlBuilder.class, method = "buildQuery")
List<User> selectByWrapper(@Param("ew") QueryWrapper<User> wrapper);
public class UserSqlBuilder {
public String buildQuery(@Param("ew") QueryWrapper<User> wrapper) {
return "SELECT * FROM user " + wrapper.getCustomSqlSegment();
}
}
- 与PageHelper整合:分页查询标准写法
java复制PageHelper.startPage(1, 10);
List<User> users = userMapper.selectList(
Wrappers.<User>query()
.eq("dept_id", 1)
.orderByDesc("create_time")
);
PageInfo<User> pageInfo = new PageInfo<>(users);
在实际项目开发中,我发现条件构造器的正确使用可以显著提升开发效率。特别是在微服务架构下,通过Wrapper对象可以在服务间传递查询条件,实现统一的查询逻辑。一个常见的坑是过度复杂的条件组合会导致SQL可读性下降,建议当条件超过5个时考虑拆分为多个查询或用XML方式实现
