稀土掘金技术社区 01月30日
妙用MyBatisPlus,12个实战技巧解锁新知识
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了MyBatisPlus的12个实战技巧,旨在提升开发效率和代码质量。文章以生动的比喻,将代码编写比作熬制羊肉汤,强调了细节和优化的重要性。通过避免使用isNull判断、明确Select字段、批量操作、使用Exists子查询、orderBy代替last等方法,可以显著提高数据库操作的性能。同时,文章还介绍了如何利用LambdaQueryWrapper保证类型安全、使用between优化范围查询、以及如何进行性能追踪和枚举映射等高级技巧。最后,文章强调了代码的优雅和高效,鼓励开发者像对待美食一样对待代码,不断雕琢,使其臻于完美。

💡 **避免使用isNull判断**:推荐使用具体的默认值代替NULL值判断,提高代码可读性和性能,并避免索引失效。

✨ **明确Select字段**:指定需要的字段而非默认选择所有字段,减少网络传输开销,并可利用索引覆盖优化查询。

🚀 **批量操作替代循环**:使用`saveBatch`等批量操作方法替代循环插入,减少数据库连接开销,提高数据一致性。

🔍 **使用Exists子查询**:使用`exists`替代`inSql`子查询,利用索引快速查询,避免加载大量数据到内存。

🔒 **使用Lambda安全排序**:使用Lambda表达式进行排序,避免SQL注入风险,保证类型安全,并提高代码可读性。

一只叫煤球的猫 2025-01-29 09:01 柬埔寨

点击关注公众号,“技术干货” 及时达!

点击关注公众号,“技术干货” 及时达!

妙用MyBatisPlus,12个实战技巧解锁新知识

前言

说起数据库ORM,我忽然想起了小时候外婆做的那锅鲜美的羊肉汤。平常人家做的羊肉汤无非是几块肉、几片姜,味道寡淡得很,喝了和喝白开水差不多。但外婆的汤,那是另一回事儿 —— 一锅汤,香气四溢,肉质软烂,汤头浓郁得能让人连碗都想舔干净。

写代码何尝不是如此?以前写Mybatis,就像是在煮一锅没有灵魂的羊肉汤:原料都在,但就是不够鲜美。代码繁琐,每写一个查询都像是在不断调味,却怎么也调不出那种令人惊艳的味道。直到遇见MyBatisPlus,一切都变了 —— 这就像是从普通的羊肉汤,突然升级到了外婆秘制的顶级羊肉汤!

MyBatisPlus就像一位精通厨艺的帮厨,它帮你处理了所有繁琐的准备工作。想要一个复杂的查询?不用自己一刀一刀地切肉、一勺一勺地调味,框架已经帮你准备好了。你只需要轻轻地指挥,代码就像汤汁一样顺滑流畅,性能更是鲜美可口。

在接下来的篇幅里,我将与你分享12个MyBatisPlus优化的"秘制配方"。相信看完这些,你写的每一行代码,都会像外婆的羊肉汤一样,让人回味无穷。

耐心看完,你一定有所收获。

避免使用isNull判断

// ❌ 不推荐LambdaQueryWrapper<User> wrapper1 = new LambdaQueryWrapper<>();wrapper1.isNull(User::getStatus);
// ✅ 推荐:使用具体的默认值LambdaQueryWrapper<User> wrapper2 = new LambdaQueryWrapper<>();wrapper2.eq(User::getStatus, UserStatusEnum.INACTIVE.getCode());

明确Select字段

// ❌ 不推荐// 默认select 所有字段List<User> users1 = userMapper.selectList(null);  
// ✅ 推荐:指定需要的字段LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.select(User::getId, User::getName, User::getAge);List<User> users2 = userMapper.selectList(wrapper);

批量操作方法替代循环

// ❌ 不推荐for (User user : userList) {    userMapper.insert(user);}
// ✅ 推荐userService.saveBatch(userList, 100); // 每批次处理100条数据
// ✅ 更优写法:自定义批次大小userService.saveBatch(userList, BatchConstants.BATCH_SIZE);

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");
// ✅ 更优写法:使用LambdaQueryWrapperwrapper.exists(orderService.lambdaQuery() .gt(Order::getAmount, 1000) .apply("order.user_id = user.id"));

使用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);

使用LambdaQuery确保类型安全

// ❌ 不推荐:字段变更后可能遗漏QueryWrapper<User> wrapper1 = new QueryWrapper<>();wrapper1.eq("name", "张三").gt("age", 18);
// ✅ 推荐LambdaQueryWrapper<User> wrapper2 = new LambdaQueryWrapper<>();wrapper2.eq(User::getName, "张三") .gt(User::getAge, 18);
// ✅ 更优写法:使用链式调用userService.lambdaQuery() .eq(User::getName, "张三") .gt(User::getAge, 18) .list();

用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);

排序字段注意索引

// ❌ 不推荐// 假设lastLoginTime无索引wrapper.orderByDesc(User::getLastLoginTime);  
// ✅ 推荐// 主键排序wrapper.orderByDesc(User::getId);
// ✅ 更优写法:组合索引排序wrapper.orderByDesc(User::getStatus) // status建立了索引 .orderByDesc(User::getId); // 主键排序

分页参数设置

// ❌ 不推荐wrapper.last("limit 1000");  // 一次查询过多数据
// ✅ 推荐Page<User> page = new Page<>(1, 10);userService.page(page, wrapper);
// ✅ 更优写法:带条件的分页查询Page<User> result = userService.lambdaQuery() .eq(User::getStatus, "active") .page(new Page<>(1, 10));

条件构造处理Null值

// ❌ 不推荐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); // 默认按创建时间倒序

⚠️ 下面就要来一些高级货了

查询性能追踪

// ❌ 不推荐:简单计时,代码冗余public List<User> listUsers(QueryWrapper<User> wrapper) {    long startTime = System.currentTimeMillis();    List<User> users = userMapper.selectList(wrapper);    long endTime = System.currentTimeMillis();    log.info("查询耗时:{}ms", (endTime - startTime));    return users;}
// ✅ 推荐:使用 Try-with-resources 自动计时public List<User> listUsersWithPerfTrack(QueryWrapper<User> wrapper) { try (PerfTracker.TimerContext ignored = PerfTracker.start()) { return userMapper.selectList(wrapper); }}
// 性能追踪工具类@Slf4jpublic 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); } } }}

枚举类型映射

// 定义枚举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 LambdaQueryWrapper<User>() .eq(User::getStatus, UserStatusEnum.NORMAL));

自动处理逻辑删除

@TableLogic  // 逻辑删除注解private Integer deleted;
// ✅ 推荐:自动过滤已删除数据public List<User> getActiveUsers() { return userMapper.selectList(null); // 自动过滤deleted=1的记录}
// 手动删除userService.removeById(1L); // 实际是更新deleted状态

乐观锁更新保护

public class Product {    @Version  // 乐观锁版本号    private Integer version;}
// ✅ 推荐:更新时自动处理版本public boolean reduceStock(Long productId, Integer count) { LambdaUpdateWrapper<Product> 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);}

递增和递减:setIncrBy 和 setDecrBy

// ❌ 不推荐:使用 setSqluserService.lambdaUpdate()    .setSql("integral = integral + 10")    .update();
// ✅ 推荐:使用 setIncrByuserService.lambdaUpdate() .eq(User::getId, 1L) .setIncrBy(User::getIntegral, 10) .update();
// ✅ 推荐:使用 setDecrByuserService.lambdaUpdate() .eq(User::getId, 1L) .setDecrBy(User::getStock, 5) .update();

总结

写代码如烹小鲜,讲究的是精细和用心。就像一碗好汤,不仅仅在于锅和火候,更在于厨师对食材的理解和尊重。MyBatisPlus的这12个优化技巧,何尝不是程序员对代码的一种尊重和雕琢?

还记得文章开头说的外婆的羊肉汤吗?优秀的代码,和一碗好汤,都需要用心。每一个细节,每一个调整,都是为了让最终的成果更加完美。MyBatisPlus就像是厨房里的得力助手,它帮你处理繁琐,让你专注于创造。

当你掌握了这些技巧,你的代码将不再是简单的指令堆砌,而是一首优雅的诗,一曲悦耳的交响乐。它们将像外婆的羊肉汤一样,散发着独特的魅力,让人回味无穷。

愿每一位开发者,都能用MyBatisPlus,煮出属于自己的"秘制汤羹"!

代码,就应该是这个样子 —— 简单而不失优雅,高效而不失温度。

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

MyBatisPlus ORM 数据库优化 代码技巧 性能提升
相关文章