说起数据库ORM我忽然想起了小时候外婆做的那锅鲜美的羊肉汤。平常人家做的羊肉汤无非是几块肉、几片姜味道寡淡得很喝了和喝白开水差不多。但外婆的汤那是另一回事儿 —— 一锅汤香气四溢肉质软烂汤头浓郁得能让人连碗都想舔干净。写代码何尝不是如此以前写Mybatis就像是在煮一锅没有灵魂的羊肉汤原料都在但就是不够鲜美。代码繁琐每写一个查询都像是在不断调味却怎么也调不出那种令人惊艳的味道。直到遇见MyBatisPlus一切都变了 —— 这就像是从普通的羊肉汤突然升级到了外婆秘制的顶级羊肉汤MyBatisPlus就像一位精通厨艺的帮厨它帮你处理了所有繁琐的准备工作。想要一个复杂的查询不用自己一刀一刀地切肉、一勺一勺地调味框架已经帮你准备好了。你只需要轻轻地指挥代码就像汤汁一样顺滑流畅性能更是鲜美可口。在接下来的篇幅里我将与你分享12个MyBatisPlus优化的秘制配方。相信看完这些你写的每一行代码都会像外婆的羊肉汤一样让人回味无穷。耐心看完你一定有所收获。1. 避免使用isNull判断// ❌ 不推荐 LambdaQueryWrapperUser wrapper1 new LambdaQueryWrapper(); wrapper1.isNull(User::getStatus); // ✅ 推荐使用具体的默认值 LambdaQueryWrapperUser wrapper2 new LambdaQueryWrapper(); wrapper2.eq(User::getStatus, UserStatusEnum.INACTIVE.getCode()); 原因:使用具体的默认值可以提高代码的可读性和维护性NULL值会使索引失效导致MySQL无法使用索引进行查询优化NULL值的比较需要特殊的处理逻辑增加了CPU开销NULL值会占用额外的存储空间影响数据压缩效率2. 明确Select字段// ❌ 不推荐默认select 所有字段 ListUser users1 userMapper.selectList(null); // ✅ 推荐指定需要的字段 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.select(User::getId, User::getName, User::getAge); ListUser users2 userMapper.selectList(wrapper); 原因:避免大量无用字段的网络传输开销可以利用索引覆盖避免回表查询减少数据库解析和序列化的负担降低内存占用特别是在大量数据查询时3. 批量操作方法替代循环// ❌ 不推荐 for (User user : userList) { userMapper.insert(user); } // ✅ 推荐 userService.saveBatch(userList, 100); // 每批次处理100条数据 // ✅ 更优写法自定义批次大小 userService.saveBatch(userList, BatchConstants.BATCH_SIZE); 原因:减少数据库连接的创建和销毁开销批量操作可以在一个事务中完成提高数据一致性数据库可以优化批量操作的执行计划显著减少网络往返次数提升吞吐量4. Exists方法子查询// ❌ 不推荐 wrapper.inSql(user_id, select user_id from order where amount 1000); // ✅ 推荐 wrapper.exists(select 1 from order where order.user_id user.id and amount 1000); // ✅ 更优写法使用LambdaQueryWrapper wrapper.exists(orderService.lambdaQuery() .gt(Order::getAmount, 1000) .apply(order.user_id user.id)); 原因:EXISTS是基于索引的快速查询可以使用到索引EXISTS在找到第一个匹配项就会停止扫描IN子查询需要加载所有数据到内存后再比较当外表数据量大时EXISTS的性能优势更明显5. 使用orderBy代替last拼接排序// ❌ 不推荐SQL注入风险 wrapper.last(ORDER BY sortField sortOrder); // ❌ 不推荐直接字符串拼接 wrapper.last(ORDER BY FIELD(status, active, pending, inactive)); // ✅ 推荐使用 Lambda 安全排序 wrapper.orderBy(true, true, User::getStatus); // ✅ 推荐多字段排序示例 wrapper.orderByAsc(User::getStatus) .orderByDesc(User::getCreateTime); 原因:直接拼接SQL容易导致SQL注入攻击动态SQL可能破坏SQL语义完整性影响SQL语句的可维护性和可读性last会绕过MyBatis-Plus的安全检查机制6. 使用LambdaQuery确保类型安全// ❌ 不推荐字段变更后可能遗漏 QueryWrapperUser wrapper1 new QueryWrapper(); wrapper1.eq(name, 张三).gt(age, 18); // ✅ 推荐 LambdaQueryWrapper LambdaQueryWrapperUser wrapper2 new LambdaQueryWrapper(); wrapper2.eq(User::getName, 张三) .gt(User::getAge, 18); // ✅ 更优写法使用链式调用 userService.lambdaQuery() .eq(User::getName, 张三) .gt(User::getAge, 18) .list(); 原因:编译期类型检查避免字段名拼写错误IDE可以提供更好的代码补全支持重构时能自动更新字段引用提高代码的可维护性和可读性7. 用between代替ge和le// ❌ 不推荐 wrapper.ge(User::getAge, 18) .le(User::getAge, 30); // ✅ 推荐 wrapper.between(User::getAge, 18, 30); // ✅ 更优写法条件动态判断 wrapper.between(ageStart ! null ageEnd ! null, User::getAge, ageStart, ageEnd); 原因:生成的SQL更简洁减少解析开销数据库优化器可以更好地处理范围查询代码更易读语义更清晰减少重复编写字段名的机会8. 排序字段注意索引// ❌ 不推荐lastLoginTime无索引 wrapper.orderByDesc(User::getLastLoginTime); // ✅ 推荐主键排序 wrapper.orderByDesc(User::getId); // ✅ 更优写法组合索引排序 wrapper.orderByDesc(User::getStatus) // status建立了索引 .orderByDesc(User::getId); // 主键排序 原因:索引天然具有排序特性可以避免额外的排序操作无索引排序会导致文件排序极大影响性能当数据量大时内存排序可能导致溢出利用索引排序可以实现流式读取9. 分页参数规范设置// ❌ 不推荐last直接限制一次性拉取大量数据 wrapper.last(limit 1000); // ✅ 推荐 PageUser page new Page(1, 10); userService.page(page, wrapper); // ✅ 更优写法带条件的分页查询 PageUser result userService.lambdaQuery() .eq(User::getStatus, active) .page(new Page(1, 10)); 原因:控制单次查询的数据量避免内存溢出提高首屏加载速度优化用户体验减少网络传输压力数据库资源利用更合理10. 条件构造优雅处理Null值// ❌ 不推荐大量if判断 if (StringUtils.isNotBlank(name)) { wrapper.eq(name, name); } if (age ! null) { wrapper.eq(age, age); } // ✅ 推荐 wrapper.eq(StringUtils.isNotBlank(name), User::getName, name) .eq(Objects.nonNull(age), User::getAge, age); // ✅ 更优写法结合业务场景 wrapper.eq(StringUtils.isNotBlank(name), User::getName, name) .eq(Objects.nonNull(age), User::getAge, age) .eq(User::getDeleted, false) // 默认查询未删除记录 .orderByDesc(User::getCreateTime); // 默认按创建时间倒序 原因:优雅处理空值避免无效条件减少代码中的if-else判断提高代码可读性防止生成冗余的SQL条件⚠️ 高级技巧篇11. 查询性能追踪// ❌ 不推荐手动计时代码冗余 public ListUser listUsers(QueryWrapperUser wrapper) { long startTime System.currentTimeMillis(); ListUser users userMapper.selectList(wrapper); long endTime System.currentTimeMillis(); log.info(查询耗时{}ms, (endTime - startTime)); return users; } // ✅ 推荐使用 Try-with-resources 自动计时 public ListUser listUsersWithPerfTrack(QueryWrapperUser wrapper) { try (PerfTracker.TimerContext ignored PerfTracker.start()) { return userMapper.selectList(wrapper); } } // 性能追踪工具类 Slf4j public class PerfTracker { private final long startTime; private final String methodName; private PerfTracker(String methodName) { this.startTime System.currentTimeMillis(); this.methodName methodName; } public static TimerContext start() { return new TimerContext(Thread.currentThread().getStackTrace()[2].getMethodName()); } public static class TimerContext implements AutoCloseable { private final PerfTracker tracker; private TimerContext(String methodName) { this.tracker new PerfTracker(methodName); } Override public void close() { long executeTime System.currentTimeMillis() - tracker.startTime; if (executeTime 500) { log.warn(慢查询告警方法 {} 耗时 {}ms, tracker.methodName, executeTime); } } } } 原因:业务代码和性能监控代码完全分离try-with-resources 即使发生异常close() 方法也会被调用确保一定会记录耗时不需要手动管理计时的开始和结束代码更加优雅12. 枚举类型映射// 定义枚举 public enum UserStatusEnum { NORMAL(1, 正常), DISABLED(0, 禁用); EnumValue // MyBatis-Plus注解 private final Integer code; private final String desc; } // 实体类自动映射 public class User { private UserStatusEnum status; } // 查询示例 userMapper.selectList( new LambdaQueryWrapperUser() .eq(User::getStatus, UserStatusEnum.NORMAL) ); 原因:类型安全自动处理数据库和枚举转换避免魔法值代码可读性更强补充拓展实用功能1. 自动逻辑删除// 实体字段标记 TableLogic private Integer deleted; // 查询自动过滤已删除数据 public ListUser getActiveUsers() { return userMapper.selectList(null); // 自动过滤deleted1的记录 } // 删除操作实际执行更新 userService.removeById(1L); 原因:数据不丢失支持数据恢复查询自动过滤已删除数据减少手动编写删除逻辑注意XML自定义SQL需要手动拼接deleted 02. 乐观锁并发更新保护// 实体添加版本字段 public class Product { Version // 乐观锁版本号 private Integer version; } // 更新时自动处理版本 public boolean reduceStock(Long productId, Integer count) { LambdaUpdateWrapperProduct wrapper new LambdaUpdateWrapper(); wrapper.eq(Product::getId, productId) .ge(Product::getStock, count); Product product new Product(); product.setStock(product.getStock() - count); return productService.update(product, wrapper); } 原因:防止并发冲突自动处理版本控制简化并发更新逻辑提高数据一致性3. 字段自增/自减 setIncrBy / setDecrBy// ❌ 不推荐手写setSql存在注入风险 userService.lambdaUpdate() .setSql(integral integral 10) .update(); // ✅ 推荐积分自增 userService.lambdaUpdate() .eq(User::getId, 1L) .setIncrBy(User::getIntegral, 10) .update(); // ✅ 推荐库存自减 userService.lambdaUpdate() .eq(User::getId, 1L) .setDecrBy(User::getStock, 5) .update(); 原因:类型安全编译校验避免手动拼接sql防止sql注入代码可维护性更强逻辑清晰总结写代码如烹小鲜讲究的是精细和用心。就像一碗好汤不仅仅在于锅和火候更在于厨师对食材的理解和尊重。MyBatisPlus的这12个优化技巧何尝不是程序员对代码的一种尊重和雕琢还记得文章开头说的外婆的羊肉汤吗优秀的代码和一碗好汤都需要用心。MyBatisPlus就像是厨房里的得力助手它帮你处理繁琐让你专注于创造。当你掌握了这些技巧你的代码将不再是简单的指令堆砌而是一首优雅的诗一曲悦耳的交响乐。它们将像外婆的羊肉汤一样散发着独特的魅力让人回味无穷。愿每一位开发者都能用MyBatisPlus煮出属于自己的秘制汤羹代码就应该是这个样子 —— 简单而不失优雅高效而不失温度。