1. MyBatis-Plus 条件构造器基础解析
MyBatis-Plus 的 Wrapper 类是构建复杂查询和更新条件的关键工具。它允许开发者以链式调用的方式构造 SQL 的 WHERE 子句,提供了极大的灵活性和便利性。在实际项目中,我们主要使用以下四种核心 Wrapper 类型:
- QueryWrapper:普通查询条件构造器
- LambdaQueryWrapper:Lambda 表达式查询条件构造器
- UpdateWrapper:普通更新条件构造器
- LambdaUpdateWrapper:Lambda 表达式更新条件构造器
1.1 基础条件构造方法
最常用的基础条件构造方法包括:
java复制// 等于条件
eq(R column, Object val)
// 不等于条件
ne(R column, Object val)
// 大于条件
gt(R column, Object val)
// 大于等于条件
ge(R column, Object val)
// 小于条件
lt(R column, Object val)
// 小于等于条件
le(R column, Object val)
这些方法都有对应的带 condition 参数的重载版本,允许我们根据条件动态添加查询条件:
java复制eq(boolean condition, R column, Object val)
1.2 条件构造实战示例
假设我们有一个用户表 user,需要查询年龄大于18岁的用户:
java复制QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 18);
对应的 Lambda 表达式版本:
java复制LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.gt(User::getAge, 18);
这两种方式生成的 SQL 语句相同:
sql复制SELECT * FROM user WHERE age > 18
2. 高级查询条件构建技巧
2.1 范围查询与模糊查询
MyBatis-Plus 提供了丰富的范围查询方法:
java复制// BETWEEN 条件
between(R column, Object val1, Object val2)
// NOT BETWEEN 条件
notBetween(R column, Object val1, Object val2)
模糊查询方法:
java复制// 全模糊匹配
like(R column, Object val)
// 右模糊匹配
likeLeft(R column, Object val)
// 左模糊匹配
likeRight(R column, Object val)
实际应用示例:
java复制// 查询年龄在18到30岁之间的用户
queryWrapper.between("age", 18, 30);
// 查询名字包含"王"的用户
queryWrapper.like("name", "王");
2.2 集合条件与空值处理
处理集合条件的方法:
java复制// IN 条件
in(R column, Collection<?> value)
// NOT IN 条件
notIn(R column, Collection<?> value)
空值处理的特殊方法:
java复制// IS NULL 条件
isNull(R column)
// 等于或为NULL
eqOrIsNull(R column, Object val)
实际应用示例:
java复制// 查询状态在指定集合中的用户
queryWrapper.in("status", Arrays.asList(1, 2, 3));
// 查询名字为null或等于"张三"的用户
queryWrapper.eqOrIsNull("name", "张三");
3. 复杂SQL条件构建
3.1 子查询与SQL片段
MyBatis-Plus 支持直接在条件中使用SQL片段:
java复制// 使用SQL片段作为IN条件
inSql(R column, String sqlValue)
// 使用SQL片段作为NOT IN条件
notInSql(R column, String sqlValue)
子查询示例:
java复制// 查询存在于子查询结果中的用户
queryWrapper.inSql("id", "select user_id from order where amount > 100");
3.2 动态条件拼接
使用 apply 方法可以直接拼接SQL片段:
java复制queryWrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2023-01-01");
使用 last 方法可以在SQL最后添加片段:
java复制queryWrapper.last("limit 10");
4. 逻辑组合与嵌套查询
4.1 AND/OR逻辑组合
MyBatis-Plus 提供了灵活的逻辑组合方式:
java复制// OR 连接
or()
// AND 嵌套
and(Consumer<Param> consumer)
// OR 嵌套
or(Consumer<Param> consumer)
复杂逻辑组合示例:
java复制queryWrapper.eq("status", 1)
.and(i -> i.gt("age", 18).or().lt("score", 60));
4.2 嵌套查询条件
使用 nested 方法可以创建独立的查询条件块:
java复制queryWrapper.nested(i -> i.eq("type", 1).ne("status", 0));
5. 排序与分组
5.1 排序条件
java复制// 升序排序
orderByAsc(R... columns)
// 降序排序
orderByDesc(R... columns)
// 自定义排序方向
orderBy(boolean isAsc, R... columns)
排序示例:
java复制queryWrapper.orderByAsc("age").orderByDesc("create_time");
5.2 分组与HAVING
java复制// 分组
groupBy(R... columns)
// HAVING条件
having(String sqlHaving, Object... params)
分组示例:
java复制queryWrapper.select("department", "count(*) as count")
.groupBy("department")
.having("count(*) > {0}", 10);
6. 更新操作条件构建
6.1 更新字段设置
java复制// 设置更新字段
set(R column, Object val)
// 设置SQL片段
setSql(String sql)
更新示例:
java复制UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("name", "张三")
.setSql("age = age + 1")
.eq("id", 1);
6.2 自增自减操作
java复制// 自增
setIncrBy(SFunction<T, ?> column, Number val)
// 自减
setDecrBy(SFunction<T, ?> column, Number val)
自增示例:
java复制LambdaUpdateWrapper<Product> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.setIncrBy(Product::getStock, -1)
.eq(Product::getId, 1001);
7. 实际项目中的最佳实践
7.1 条件构造的复用
可以将常用的查询条件封装为方法:
java复制public QueryWrapper<User> activeUsersWrapper() {
return new QueryWrapper<User>()
.eq("status", 1)
.gt("last_login_time", LocalDateTime.now().minusMonths(1));
}
7.2 动态条件构建技巧
利用条件参数实现动态查询:
java复制public List<User> queryUsers(String name, Integer minAge, Integer maxAge) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name)
.gt(minAge != null, User::getAge, minAge)
.lt(maxAge != null, User::getAge, maxAge);
return userMapper.selectList(wrapper);
}
7.3 性能优化建议
- 避免在循环中创建Wrapper对象
- 合理使用select()方法限制查询字段
- 对于复杂查询,考虑使用原生SQL或XML映射
- 注意索引的使用,确保条件字段有适当索引
8. 常见问题与解决方案
8.1 条件构造的常见错误
- 字段名拼写错误:使用Lambda表达式可避免此问题
- 类型不匹配:确保条件值与字段类型一致
- 空集合处理:IN条件传入空集合会导致SQL错误
8.2 复杂查询的调试技巧
- 使用getSqlSegment()方法查看生成的SQL片段
- 开启MyBatis日志查看完整SQL
- 对于复杂条件,分步构建并测试
8.3 与其他框架的整合
与PageHelper分页插件整合时,需要注意:
- 先构建Wrapper条件
- 再调用PageHelper.startPage()
- 最后执行查询
9. 高级特性与自定义扩展
9.1 自定义SQL方法
可以在Wrapper上扩展自定义方法:
java复制public class MyQueryWrapper<T> extends QueryWrapper<T> {
public MyQueryWrapper<T> today(String column) {
this.apply("date_format({0},'%Y-%m-%d') = date_format(now(),'%Y-%m-%d')", column);
return this;
}
}
9.2 条件构造器的原理分析
Wrapper的核心原理:
- 通过链式调用构建条件表达式树
- 在SQL生成时遍历表达式树
- 使用SqlInjector注入条件SQL片段
9.3 性能对比与选择建议
不同场景下的Wrapper选择:
- 简单查询:QueryWrapper
- 类型安全查询:LambdaQueryWrapper
- 复杂动态查询:自定义Wrapper
- 极高性能要求:原生SQL
10. 实战案例:复杂报表查询
假设我们需要构建一个销售报表查询:
java复制public List<SalesReport> generateSalesReport(LocalDate startDate, LocalDate endDate,
List<Integer> productIds, String region) {
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.between(Order::getOrderDate, startDate, endDate)
.in(productIds != null && !productIds.isEmpty(), Order::getProductId, productIds)
.eq(StringUtils.isNotBlank(region), Order::getRegion, region)
.groupBy(Order::getProductId, Order::getRegion)
.select(Order::getProductId, Order::getRegion,
"sum(amount) as totalAmount", "count(*) as orderCount")
.orderByDesc("totalAmount");
return orderMapper.selectSalesReport(wrapper);
}
对应的Mapper接口:
java复制@Select("select product_id, region, ${ew.sqlSelect} from orders ${ew.customSqlSegment}")
List<SalesReport> selectSalesReport(@Param("ew") LambdaQueryWrapper<Order> wrapper);
11. 条件构造器的边界情况处理
11.1 空值处理策略
- eq(null)会生成 IS NULL条件
- 使用ObjectUtil.isNull()判断更安全
- 对于字符串空值,注意区分null和""
11.2 特殊字符转义
- LIKE条件中的%和_需要转义
- 使用EscapeUtils处理特殊字符
- 考虑使用PreparedStatement参数化查询
11.3 多表关联查询
虽然Wrapper主要设计用于单表查询,但也可以通过以下方式实现多表查询:
- 使用join()方法(部分版本支持)
- 在自定义SQL中使用Wrapper条件
- 使用@TableField注解处理字段映射
12. 未来发展与替代方案
12.1 MyBatis-Plus的条件构造器演进
- 3.5.x版本对Lambda表达式的增强
- 对Kotlin DSL的支持
- 更智能的条件推断
12.2 其他查询构建方案对比
- QueryDSL:类型安全,但学习成本高
- JOOQ:功能强大,但需要商业授权
- Spring Data JPA:规范统一,但灵活性不足
12.3 条件构造的最佳实践总结
- 简单条件使用Wrapper
- 复杂查询考虑自定义SQL
- 保持一致性:团队统一风格
- 文档化:复杂条件添加注释
在实际项目中,MyBatis-Plus的条件构造器能够显著提高开发效率,特别是在需要构建复杂动态查询的场景下。通过合理使用Wrapper及其各种方法,可以避免大量重复的SQL编写工作,同时保持代码的可读性和可维护性。
