小熊学Java:https://javaxiaobear.cn
【2024年】编程资料大合集,年度精选资源一键收藏
一、避免使用isNull判断
在查询过程中,我们可能会遇到需要判断某个字段是否为NULL的情况。然而,频繁使用isNull判断不仅会降低代码的可读性,还可能影响数据库的性能。例如:
// ❌ 不推荐
LambdaQueryWrapper<User> wrapper1 = new LambdaQueryWrapper<>();
wrapper1.isNull(User::getStatus);
优化建议:
- 使用具体的默认值代替isNull判断。这样可以提高代码的可读性和维护性,同时避免NULL值带来的索引失效、CPU开销增加等问题。
// ✅ 推荐:使用具体的默认值
LambdaQueryWrapper<User> wrapper2 = new LambdaQueryWrapper<>();
wrapper2.eq(User::getStatus, UserStatusEnum.INACTIVE.getCode());
原因:
- 使用具体的默认值可以提高代码的可读性和维护性
- NULL值会使索引失效,导致MySQL无法使用索引进行查询优化
- NULL值的比较需要特殊的处理逻辑,增加了CPU开销
- NULL值会占用额外的存储空间,影响数据压缩效率
二、明确Select字段
在查询数据时,如果不明确指定需要查询的字段,MyBatisPlus会默认查询所有字段。这种方式虽然简单,但可能会导致不必要的性能开销。例如:
// ❌ 不推荐
// 默认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);
}
优化建议:
- 使用MyBatisPlus提供的批量操作方法,如saveBatch。这样可以在一个事务中完成批量操作,提高数据一致性,减少网络往返次数,提升吞吐量。
// ✅ 推荐
userService.saveBatch(userList, 100); // 每批次处理100条数据
// ✅ 更优写法:自定义批次大小
userService.saveBatch(userList, BatchConstants.BATCH_SIZE);
四、Exists方法子查询
在某些场景下,我们需要根据子查询的结果来判断是否存在满足条件的记录。使用Exists方法可以提高查询效率。例如:
// ❌ 不推荐
wrapper.inSql("user_id", "select user_id from order where amount > 1000");
优化建议:
- 使用Exists方法进行子查询。Exists是基于索引的快速查询,可以在找到第一个匹配项后停止扫描,避免加载所有数据到内存后再比较。
// ✅ 推荐
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"));
五、使用orderBy代替last
在进行排序操作时,直接拼接SQL字符串不仅容易导致SQL注入攻击,还会影响SQL语句的可维护性和可读性。例如:
// ❌ 不推荐:SQL注入风险
wrapper.last("ORDER BY " + sortField + " " + sortOrder);
// ❌ 不推荐:直接字符串拼接
wrapper.last("ORDER BY FIELD(status, 'active', 'pending', 'inactive')");
优化建议:
- 使用LambdaQueryWrapper的orderBy方法进行安全排序。这样可以避免SQL注入攻击,确保SQL语句的语义完整性。
// ✅ 推荐:使用 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进行查询。这样可以在编译期进行类型检查,避免字段名拼写错误,提高代码的可维护性和可读性。
// ✅ 推荐
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
在进行范围查询时,使用ge和le方法需要重复编写字段名,代码不够简洁。例如:
// ❌ 不推荐
wrapper.ge(User::getAge, 18)
.le(User::getAge, 30);
优化建议:
- 使用between方法进行范围查询。这样可以生成更简洁的SQL,减少解析开销,提高代码的可读性。
// ✅ 推荐
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"); // 一次查询过多数据
优化建议:
- 使用MyBatisPlus提供的Page对象进行分页查询。这样可以控制单次查询的数据量,提高首屏加载速度,减少网络传输压力。
// ✅ 推荐
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值
在构造查询条件时,合理处理Null值可以避免生成无效的SQL条件。例如:
// ❌ 不推荐
if (StringUtils.isNotBlank(name)) {
wrapper.eq("name", name);
}
if (age != null) {
wrapper.eq("age", age);
}
优化建议:
- 使用LambdaQueryWrapper的条件构造方法优雅处理Null值。这样可以减少代码中的if-else判断,提高代码的可读性。
// ✅ 推荐
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自动计时。这样可以确保一定会记录耗时,使业务代码和性能监控代码完全分离,代码更加优雅。
// ✅ 推荐:使用 Try-with-resources 自动计时
public List<User> listUsersWithPerfTrack(QueryWrapper<User> 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);
}
}
}
}
十二、枚举类型映射
在处理枚举类型时,使用MyBatisPlus提供的枚举类型映射功能可以提高代码的类型安全性和可读性。例如:
// 定义枚举
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)
);
优化建议:
- 使用@EnumValue注解进行枚举类型映射。这样可以自动处理数据库和枚举转换,避免魔法值,提高代码的可读性。
十三、自动处理逻辑删除
在进行数据删除操作时,使用逻辑删除可以避免数据丢失,同时支持数据恢复。例如:
@TableLogic // 逻辑删除注解
private Integer deleted;
// ✅ 推荐:自动过滤已删除数据
public List<User> getActiveUsers() {
return userMapper.selectList(null); // 自动过滤deleted=1的记录
}
// 手动删除
userService.removeById(1L); // 实际是更新deleted状态
优化建议:
- 使用@TableLogic注解进行逻辑删除。这样可以自动过滤已删除数据,减少手动编写删除逻辑。
十四、乐观锁更新保护
在进行并发更新操作时,使用乐观锁可以有效防止并发冲突。例如:
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);
}
优化建议:
- 使用@Version注解进行乐观锁控制。这样可以自动处理版本控制,简化并发更新逻辑,提高数据一致性。
十五、递增和递减:setIncrBy 和 setDecrBy
在进行字段递增或递减操作时,使用setIncrBy和setDecrBy方法可以提高代码的类型安全性和可维护性。例如:
// ❌ 不推荐:使用 setSql
userService.lambdaUpdate()
.setSql("integral = integral + 10")
.update();
// ✅ 推荐:使用 setIncrBy
userService.lambdaUpdate()
.eq(User::getId, 1L)
.setIncrBy(User::getIntegral, 10)
.update();
// ✅ 推荐:使用 setDecrBy
userService.lambdaUpdate()
.eq(User::getId, 1L)
.setDecrBy(User::getStock, 5)
.update();
优化建议:
- 使用setIncrBy和setDecrBy方法进行字段递增或递减操作。这样可以避免手动拼接sql,防止sql注入,提高代码的可维护性和可读性。
总结
代码如烹小鲜,讲究的是精细和用心。MyBatisPlus的这15个优化技巧,何尝不是程序员对代码的一种尊重和雕琢?掌握这些技巧后,你的代码将不再是简单的指令堆砌,而是一首优雅的诗,一曲悦耳的交响乐。它们将像外婆的羊肉汤一样,散发着独特的魅力,让人回味无穷。愿每一位开发者,都能用MyBatisPlus,煮出属于自己的“秘制汤羹”!