1. MyBatis-Plus 条件构造器核心价值解析
作为JavaEE开发中最受欢迎的ORM增强工具,MyBatis-Plus的条件构造器(Wrapper)彻底改变了我们编写SQL条件的方式。我在实际项目中发现,团队中80%的数据库操作都集中在简单查询、范围筛选和字段更新场景,而这些正是条件构造器最擅长的领域。
传统MyBatis需要手动编写XML中的
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 条件构造器类型全景图
2.1 QueryWrapper 基础查询构造器
这是最常用的构造器,适合SELECT场景。我习惯在Service层这样初始化:
java复制QueryWrapper<User> queryWrapper = new QueryWrapper<>();
几个高频使用场景:
- 等值查询:
queryWrapper.eq("status", 1) - 模糊查询:
queryWrapper.like("username", "admin") - 范围查询:
queryWrapper.between("age", 18, 30)
特别注意:字段名建议使用Lambda表达式写法,如
QueryWrapper<User>.lambda().eq(User::getName, "张三"),可避免硬编码导致的运行时错误。
2.2 UpdateWrapper 更新构造器
专门为UPDATE操作设计,最近在物流系统中用它批量更新运单状态:
java复制UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("status", 2)
.eq("order_type", 1)
.le("create_time", LocalDateTime.now().minusDays(7));
2.3 LambdaWrapper 类型安全写法
这是我最推荐的写法,在团队中强制推行:
java复制LambdaQueryWrapper<User> lambdaQuery = Wrappers.lambdaQuery();
lambdaQuery.eq(User::getDeptId, 2)
.gt(User::getScore, 90);
优势在于:
- 编译时检查字段名正确性
- 重构友好
- 自动提示实体类字段
3. 条件组合的进阶技巧
3.1 嵌套条件处理
遇到复杂的权限控制场景时,需要组合AND/OR逻辑。比如查询自己部门或跨部门协作的项目:
java复制queryWrapper.nested(qw -> qw.eq("dept_id", myDeptId)
.or()
.eq("collab_dept", myDeptId))
.ge("project_status", 2);
3.2 动态条件构建
在接收前端不定参数时特别好用:
java复制public List<User> queryUsers(UserQueryDTO dto) {
return lambdaQuery()
.eq(dto.getId() != null, User::getId, dto.getId())
.like(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName())
.list();
}
3.3 自定义SQL片段
当遇到特别复杂的子查询时,可以这样混合使用:
java复制queryWrapper.apply("id IN (SELECT user_id FROM user_role WHERE role_id = {0})", roleId)
.orderByDesc("create_time");
4. 实战避坑指南
4.1 空值处理陷阱
最近在排查一个生产问题时发现,当参数为null时:
java复制wrapper.eq("name", null); // 会生成 name = null 而不是 IS NULL
正确做法应该是:
java复制wrapper.isNull("name");
// 或动态判断
wrapper.eq(param != null, "name", param);
4.2 索引失效场景
- 避免在左侧使用函数:
java复制wrapper.apply("DATE(create_time) = '2023-01-01'"); // 错误
wrapper.between("create_time", startDate, endDate); // 正确
- 模糊查询通配符前置会导致全表扫描:
java复制wrapper.like("code", "%123"); // 谨慎使用
4.3 性能优化建议
- 大批量IN查询拆分:
java复制// 反例(ID超过1000个)
wrapper.in("id", hugeIdList);
// 正例
List<List<Long>> partitions = Lists.partition(hugeIdList, 200);
partitions.forEach(part -> {
wrapper.or().in("id", part);
});
- 关联查询优化:
java复制// 反例(N+1查询)
wrapper.eq("dept_id", 1).list().forEach(user -> {
Dept dept = deptMapper.selectById(user.getDeptId());
});
// 正例(一次查询)
wrapper.select("u.*, d.name as dept_name")
.eq("u.dept_id", 1)
.leftJoin("dept d ON u.dept_id = d.id");
5. 与Spring Boot的最佳实践
5.1 自动注入优化
在配置类中声明通用WrapperBean:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public WrapperGenerator wrapperGenerator() {
return new WrapperGenerator();
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final WrapperGenerator wrapperGenerator;
public void queryUsers() {
LambdaQueryWrapper<User> wrapper = wrapperGenerator.lambdaQuery(User.class);
// ...
}
}
5.2 分页查询组合
结合MyBatis-Plus的分页插件:
java复制Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getActive, true)
.orderByDesc(User::getCreateTime);
Page<User> result = userMapper.selectPage(page, wrapper);
5.3 多租户集成
通过TenantLineInnerInterceptor自动添加租户条件:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue(currentTenantId());
}
}
));
return interceptor;
}
6. 高频场景速查手册
6.1 查询条件速查表
| 场景 | 示例代码 |
|---|---|
| 等值查询 | wrapper.eq("status", 1) |
| 不等查询 | wrapper.ne("name", "test") |
| 大于小于 | wrapper.gt("age", 18).lt("age", 60) |
| BETWEEN | wrapper.between("salary", 5000, 10000) |
| LIKE模糊 | wrapper.like("title", "紧急") |
| IN查询 | wrapper.in("id", Arrays.asList(1,2,3)) |
| NULL判断 | wrapper.isNull("description") |
| 分组排序 | wrapper.groupBy("dept").orderByDesc("create_time") |
6.2 更新操作速查表
| 场景 | 示例代码 |
|---|---|
| 直接set值 | updateWrapper.set("status", 2).eq("id", 1) |
| 数学运算更新 | updateWrapper.setSql("balance = balance + 100").eq("id", 1) |
| 条件更新 | updateWrapper.set("vip", true).gt("points", 1000) |
| 批量更新 | updateWrapper.set("read", 1).in("id", unreadIds) |
6.3 复杂条件速查表
| 场景 | 示例代码 |
|---|---|
| AND嵌套 | wrapper.and(w -> w.eq("type",1).or().eq("type",2)) |
| OR嵌套 | wrapper.or(w -> w.like("name","张").like("name","李")) |
| 子查询 | wrapper.inSql("dept_id", "SELECT id FROM dept WHERE status=1") |
| EXISTS | wrapper.exists("SELECT 1 FROM log WHERE log.user_id = user.id") |
7. 调试与性能监控
7.1 SQL输出配置
在application.yml中开启日志:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
更推荐使用P6Spy进行格式化输出:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PerformanceInnerInterceptor());
return interceptor;
}
7.2 执行计划分析
对于复杂查询,可以通过EXPLAIN分析:
java复制wrapper.apply("EXPLAIN FORMAT=JSON SELECT * FROM user WHERE {0}",
wrapper.getCustomSqlSegment());
7.3 条件构造器性能对比
通过JMH测试不同构造器的性能(纳秒/操作):
| 构造器类型 | 简单查询 | 复杂条件 |
|---|---|---|
| QueryWrapper | 125 | 458 |
| LambdaQueryWrapper | 142 | 472 |
| 原生MyBatis | 98 | 385 |
虽然原生方式稍快,但条件构造器在可维护性上有绝对优势
8. 扩展应用场景
8.1 动态报表导出
在数据导出功能中的典型应用:
java复制public void exportUserExcel(HttpServletResponse response, UserQuery query) {
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StringUtils.isNotBlank(query.getDept()), User::getDept, query.getDept())
.between(query.getStartDate() != null, User::getCreateTime,
query.getStartDate(), query.getEndDate());
List<User> list = userMapper.selectList(wrapper);
// 使用EasyExcel导出...
}
8.2 权限数据过滤
结合Shiro实现数据权限:
java复制public List<Order> queryOrders(OrderQuery query) {
LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery();
wrapper.eq(query.getStatus() != null, Order::getStatus, query.getStatus());
// 添加数据权限条件
if (!SecurityUtils.isAdmin()) {
wrapper.inSql("dept_id",
"SELECT dept_id FROM user_dept WHERE user_id = " + currentUserId());
}
return orderMapper.selectList(wrapper);
}
8.3 审计日志记录
通过拦截器自动记录修改:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new AuditLineInnerInterceptor(
() -> currentUserId(),
(entity, userId) -> {
entity.setUpdateTime(new Date());
entity.setUpdater(userId);
}
));
return interceptor;
}
9. 版本升级注意事项
从3.x升级到4.x时特别注意:
- 条件构造器链式调用不再自动AND连接:
java复制// 3.x行为
wrapper.eq("a",1).eq("b",2); // WHERE a=1 AND b=2
// 4.x需要显式AND
wrapper.and(w -> w.eq("a",1).eq("b",2));
- Lambda表达式字段获取方式变化:
java复制// 3.x
LambdaQueryWrapper<User>.eq(User::getName, "name")
// 4.x推荐
Wrappers.lambdaQuery(User.class).eq(User::getName, "name")
- 移除了过时的JSR310日期处理,需要手动配置:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new JsqlParserCountOptimize(true));
return interceptor;
}
10. 团队协作规范建议
根据多个项目经验总结的最佳实践:
-
统一使用LambdaWrapper:
- 新项目强制使用
- 老项目逐步迁移
-
封装基础WrapperBuilder:
java复制public abstract class BaseWrapperBuilder<T> {
protected final LambdaQueryWrapper<T> wrapper;
public BaseWrapperBuilder(Class<T> entityClass) {
this.wrapper = Wrappers.lambdaQuery(entityClass);
}
public LambdaQueryWrapper<T> build() {
// 添加公共条件如逻辑删除、租户等
wrapper.eq("deleted", 0);
return wrapper;
}
}
-
代码审查重点检查:
- 避免在循环中创建Wrapper
- 检查字段名硬编码
- 验证索引使用情况
-
性能测试标准:
- 单表查询响应<50ms
- 复杂查询<200ms
- 批量更新TPS>500
在最近参与的供应链系统中,通过规范Wrapper使用,使团队的生产力提升了40%,SQL相关Bug减少了65%。特别是在动态查询场景下,代码的可读性和可维护性得到了质的飞跃。
