MyBatis-Plus的Wrapper用错了?盘点LambdaQueryWrapper和UpdateWrapper的5个高频踩坑点
MyBatis-Plus的Wrapper用错了盘点LambdaQueryWrapper和UpdateWrapper的5个高频踩坑点在Java持久层框架中MyBatis-Plus因其强大的Wrapper条件构造器而备受开发者青睐。然而在实际项目中不少中高级开发者依然会在LambdaQueryWrapper和UpdateWrapper的使用上栽跟头。本文将揭示那些看似简单却容易导致生产事故的Wrapper使用误区。1. 联表查询中的性能陷阱许多开发者习惯在联表查询时直接使用Wrapper构建复杂条件却忽略了SQL执行的本质。例如下面这段典型的问题代码LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getDepartmentId, departmentId) .like(User::getName, 张) .orderByDesc(User::getCreateTime); ListUser users userMapper.selectList(wrapper);当User表与Department表关联时这样的写法会导致全表扫描。更优的做法是// 使用Select注解自定义SQL Select(SELECT u.* FROM user u LEFT JOIN department d ON u.department_idd.id WHERE d.id#{depId} AND u.name LIKE CONCAT(%,#{name},%) ORDER BY u.create_time DESC) ListUser selectUsersWithDepartment(Param(depId) Long depId, Param(name) String name);关键对比方式执行计划索引利用率适合场景纯Wrapper可能全表扫描低单表简单查询自定义SQL可使用联合索引高复杂联表查询提示当查询涉及3张以上表关联时应优先考虑XML映射文件或注解方式编写明确SQL。2. Lambda表达式的NPE隐患Lambda表达式方法引用虽然优雅但在某些情况下会引发空指针异常public ListUser findUsers(UserQuery query) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); // 当query为null时这里会抛出NPE wrapper.eq(query ! null, User::getAge, query.getAge()); return userMapper.selectList(wrapper); }正确的防御性写法应该是wrapper.eq(ObjectUtils.isNotEmpty(query) query.getAge() ! null, User::getAge, Optional.ofNullable(query).map(UserQuery::getAge).orElse(null));常见NPE触发场景方法引用左侧对象可能为null集合属性直接调用stream()链式调用中任一环节为null3. UpdateWrapper的set逻辑误区UpdateWrapper的set方法使用不当会导致更新异常看这个典型错误案例UpdateWrapperUser wrapper new UpdateWrapper(); wrapper.eq(status, 1) .set(age, user.getAge()) // 当user为null时age会被设为null .set(name, admin); userMapper.update(null, wrapper);更安全的更新方式应该是User updateUser new User(); updateUser.setName(admin); UpdateWrapperUser wrapper new UpdateWrapper(); wrapper.eq(status, 1); // 仅当age非空时才更新该字段 if (user ! null user.getAge() ! null) { wrapper.set(age, user.getAge()); } userMapper.update(updateUser, wrapper);字段更新策略对比策略优点缺点适用场景set直接赋值简洁可能覆盖为null明确要更新的字段实体类更新类型安全需创建对象多字段更新条件判断更新精确控制代码冗长部分字段更新4. 分页查询与Wrapper的配合问题分页查询时直接使用Wrapper可能导致分页不准确或性能问题PageUser page new Page(1, 10); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getActive, true) .orderByDesc(User::getScore); PageUser result userMapper.selectPage(page, wrapper);当数据量大时这种写法会有两个问题先执行count查询再执行limit查询排序字段无索引时性能极差优化方案// 方案1使用自定义分页SQL Select(SELECT * FROM user WHERE is_active1 ORDER BY score DESC LIMIT #{offset},#{size}) ListUser selectActiveUsers(Param(offset) long offset, Param(size) long size); // 方案2添加缓存计数 Long total cacheHelper.get(active_user_count); if (total null) { total userMapper.selectCount(wrapper); cacheHelper.set(active_user_count, total, 60); } ListUser records userMapper.selectActiveUsers(0, 10);5. Wrapper序列化与分布式缓存在分布式环境下直接序列化Wrapper可能引发的问题往往被忽视// 将Wrapper存入Redis缓存 String key user_query: md5(queryParams); LambdaQueryWrapperUser wrapper buildWrapper(queryParams); redisTemplate.opsForValue().set(key, wrapper, 1, TimeUnit.HOURS); // 从缓存获取后使用 LambdaQueryWrapperUser cachedWrapper (LambdaQueryWrapperUser) redisTemplate.opsForValue().get(key); ListUser users userMapper.selectList(cachedWrapper); // 可能抛出序列化异常安全的使用模式应该是// 自定义Wrapper的序列化转换 public class WrapperSerializer { public static String serialize(AbstractWrapper wrapper) { return JSON.toJSONString(wrapper.getParamNameValuePairs()); } public static LambdaQueryWrapperUser deserialize(String json, ClassUser entityClass) { MapString, Object params JSON.parseObject(json); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); params.forEach((k, v) - wrapper.eq(SqlUtils.camelToUnderline(k), v)); return wrapper; } }分布式环境下Wrapper使用建议避免直接序列化Wrapper实例存储查询参数而非Wrapper对象考虑使用自定义查询条件对象对复杂条件建立版本控制机制