解锁MyBatisPlus高阶查询:5个核心方法解决90%业务难题
在Java后端开发中,数据查询是最基础却最频繁的操作。许多开发者在使用MyBatisPlus时,往往止步于简单的selectById,却不知框架早已为各种复杂场景准备了优雅的解决方案。本文将带您突破基础CRUD的局限,掌握五个核心查询方法的实战技巧,让您的代码效率提升至少50%。
1. 从单条查询到批量处理:选择正确的查询姿势
1.1 selectById:简单背后的陷阱
selectById是最基础的查询方法,但90%的开发者都没用对。考虑以下典型场景:
// 典型错误用法:循环查询 List<Long> ids = Arrays.asList(1L, 2L, 3L); List<User> users = new ArrayList<>(); for(Long id : ids) { users.add(userMapper.selectById(id)); }这种写法会产生N+1查询问题,当ids列表较大时性能急剧下降。正确的做法是使用selectBatchIds:
// 优化后的批量查询 List<User> users = userMapper.selectBatchIds(ids);性能对比:
| 查询方式 | 100条记录耗时(ms) | 数据库连接次数 |
|---|---|---|
| 循环selectById | 320 | 100 |
| selectBatchIds | 45 | 1 |
1.2 selectOne的精准定位艺术
selectOne特别适合需要确保唯一性约束的查询场景,比如用户名、手机号等唯一字段的查询:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("username", "admin") .eq("status", 1); // 活跃用户 User admin = userMapper.selectOne(wrapper);但要注意两个常见陷阱:
- 结果不唯一时不会报错,而是静默返回第一条
- 无结果时返回null而非空对象
提示:对于关键业务查询,建议先使用selectCount确认唯一性,再调用selectOne
2. 动态条件查询的两种范式
2.1 selectByMap的快速过滤
当查询条件来自前端动态参数时,selectByMap提供了最快捷的实现方式:
Map<String, Object> params = new HashMap<>(); params.put("department", "研发部"); params.put("level", "P7"); List<User> devUsers = userMapper.selectByMap(params);适用场景:
- 简单的等值查询
- 条件字段固定且有限
- 快速原型开发阶段
2.2 QueryWrapper的灵活之道
对于复杂查询条件,QueryWrapper才是终极武器。它支持包括模糊查询、范围查询、嵌套查询等所有SQL能表达的条件:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("name", "张") .between("age", 25, 35) .inSql("dept_id", "SELECT id FROM department WHERE name LIKE '%技术%'") .orderByDesc("create_time"); List<User> users = userMapper.selectList(wrapper);条件构造器常用方法:
| 方法 | 等价SQL | 适用场景 |
|---|---|---|
| eq() | = | 精确匹配 |
| ne() | != | 不等条件 |
| gt() | > | 大于条件 |
| like() | LIKE '%值%' | 模糊查询 |
| between() | BETWEEN | 范围查询 |
| in() | IN | 多值匹配 |
3. 分页查询的性能优化秘籍
3.1 基础分页实现
selectPage是处理大数据量查询的必备工具,但很多开发者只停留在基础用法:
Page<User> page = new Page<>(1, 10); // 第一页,每页10条 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("status", 1); IPage<User> result = userMapper.selectPage(page, wrapper);3.2 高级分页技巧
技巧1:禁用COUNT查询当确定不需要总记录数时:
Page<User> page = new Page<>(1, 10, false); // 第三个参数禁用count技巧2:自定义COUNT SQL对于复杂查询,可以优化COUNT语句:
@Select("SELECT * FROM user ${ew.customSqlSegment}") @Select("SELECT COUNT(1) FROM user ${ew.customSqlSegment}") IPage<User> selectUserPage(Page<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);分页参数调优建议:
| 场景 | 每页条数 | 推荐配置 |
|---|---|---|
| 后台管理系统 | 10-50 | 默认count查询 |
| 移动端列表 | 20-100 | 禁用count |
| 数据导出 | 500-1000 | 自定义count |
4. 实战场景解决方案
4.1 多条件动态搜索
电商平台商品筛选的典型实现:
public Page<Product> searchProducts(ProductQuery query) { QueryWrapper<Product> wrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(query.getKeyword())) { wrapper.and(w -> w.like("name", query.getKeyword()) .or() .like("description", query.getKeyword())); } if (query.getCategoryId() != null) { wrapper.eq("category_id", query.getCategoryId()); } if (query.getMinPrice() != null && query.getMaxPrice() != null) { wrapper.between("price", query.getMinPrice(), query.getMaxPrice()); } if (CollectionUtils.isNotEmpty(query.getBrandIds())) { wrapper.in("brand_id", query.getBrandIds()); } return productMapper.selectPage(new Page<>(query.getPage(), query.getSize()), wrapper); }4.2 树形结构查询
部门树形结构的递归查询方案:
public List<Department> getDepartmentTree(Long parentId) { QueryWrapper<Department> wrapper = new QueryWrapper<>(); wrapper.eq(parentId != null, "parent_id", parentId) .orderByAsc("sort_order"); List<Department> departments = departmentMapper.selectList(wrapper); departments.forEach(dept -> { List<Department> children = getDepartmentTree(dept.getId()); dept.setChildren(children); }); return departments; }5. 性能陷阱与最佳实践
5.1 N+1查询问题
错误示例:
List<Order> orders = orderMapper.selectList(null); orders.forEach(order -> { User user = userMapper.selectById(order.getUserId()); order.setUser(user); });优化方案:
// 先批量查询所有用户ID List<Order> orders = orderMapper.selectList(null); Set<Long> userIds = orders.stream() .map(Order::getUserId) .collect(Collectors.toSet()); // 一次性查询所有用户 Map<Long, User> userMap = userMapper.selectBatchIds(userIds) .stream() .collect(Collectors.toMap(User::getId, Function.identity())); // 内存关联 orders.forEach(order -> order.setUser(userMap.get(order.getUserId())));5.2 索引失效场景
以下写法会导致索引失效:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.apply("DATE_FORMAT(create_time,'%Y-%m') = '2023-01'");应改为:
wrapper.between("create_time", "2023-01-01", "2023-01-31");常见索引失效情况:
- 对字段使用函数操作
- 隐式类型转换
- 前导模糊查询(
LIKE '%xxx') - 使用
!=或<>操作符 - 组合索引不满足最左前缀原则