MyBatis-Plus数据查询与分页实践指南

一、MyBatis-Plus查询方法体系概览

MyBatis-Plus作为MyBatis的增强工具,在保留原生功能基础上提供了丰富的查询接口。其核心查询方法可分为三大类:

  1. 基础查询:基于主键的精确查询
  2. 条件查询:通过Wrapper构建动态SQL
  3. 分页查询:集成物理分页与逻辑分页能力

这些方法通过统一的Service接口暴露,开发者无需编写XML映射文件即可完成复杂查询。以User实体为例,其对应的Mapper接口继承BaseMapper<User>后自动获得这些能力。

二、基础查询方法详解

2.1 selectById方法

  1. // 根据主键查询单条记录
  2. User user = userMapper.selectById(1L);

该方法适用于已知主键值的精确查询场景,其内部实现通过<select resultMap="BaseResultMap">标签生成SQL,支持所有主键类型(Long/String/UUID等)。当查询结果为空时返回null,而非空集合。

2.2 selectOne方法

  1. // 根据条件查询单条记录
  2. LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
  3. wrapper.eq(User::getAge, 25)
  4. .last("LIMIT 1"); // 显式限制返回条数
  5. User user = userMapper.selectOne(wrapper);

该方法要求查询条件必须唯一,当返回多条记录时会抛出TooManyResultsException。典型应用场景包括:

  • 唯一性校验(如用户名查重)
  • 精确匹配查询
  • 结合@TableId注解的主键查询替代方案

三、条件查询方法解析

3.1 Wrapper体系架构

MyBatis-Plus提供四种Wrapper实现:

  • QueryWrapper:基础条件构造器
  • LambdaQueryWrapper:支持Lambda表达式的类型安全构造器
  • UpdateWrapper:更新条件构造器
  • LambdaUpdateWrapper:Lambda风格的更新构造器

3.2 链式调用示例

  1. // 复杂条件查询示例
  2. List<User> users = userMapper.selectList(
  3. Wrappers.<User>lambdaQuery()
  4. .between(User::getAge, 20, 30)
  5. .like(User::getName, "张")
  6. .isNotNull(User::getEmail)
  7. .orderByDesc(User::getCreateTime)
  8. );

该查询等价于以下SQL:

  1. SELECT * FROM user
  2. WHERE age BETWEEN 20 AND 30
  3. AND name LIKE '%张%'
  4. AND email IS NOT NULL
  5. ORDER BY create_time DESC

3.3 高级查询技巧

  1. 嵌套条件:使用nested()方法实现括号逻辑

    1. wrapper.nested(w -> w.eq("role", "admin").or().eq("role", "super_admin"))
    2. .eq("status", 1);
  2. SQL注入防护:自动对参数进行预编译处理

  3. 自定义SQL片段:通过apply()方法插入原生SQL
    1. wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2023-01-01");

四、分页查询实现方案

4.1 基础分页配置

首先需要配置分页插件:

  1. @Configuration
  2. public class MybatisPlusConfig {
  3. @Bean
  4. public MybatisPlusInterceptor mybatisPlusInterceptor() {
  5. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  6. interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
  7. return interceptor;
  8. }
  9. }

4.2 分页查询示例

  1. // 创建分页对象
  2. Page<User> page = new Page<>(1, 10); // 当前页,每页大小
  3. // 构建查询条件
  4. LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
  5. wrapper.ge(User::getAge, 18);
  6. // 执行分页查询
  7. IPage<User> userPage = userMapper.selectPage(page, wrapper);
  8. // 获取分页信息
  9. long total = userPage.getTotal(); // 总记录数
  10. List<User> records = userPage.getRecords(); // 当前页数据
  11. long pages = userPage.getPages(); // 总页数

4.3 分页参数优化

  1. 性能优化:大数据量分页时建议使用last("LIMIT {0},{1}", offset, size)替代Page对象
  2. 计数优化:对于已知总量的场景,可通过optimizeJoin参数关闭关联查询的COUNT优化
  3. 自定义COUNT:重写selectCount方法实现复杂计数逻辑

4.4 多表分页查询

对于关联查询的分页,推荐两种方案:

  1. 先查询主表ID:分页查询主表ID后,再通过IN查询详情
    ```java
    // 分页查询ID
    Page idPage = new Page<>(1, 100);
    IPage ids = userMapper.selectObjsPage(idPage,
    Wrappers.lambdaQuery().select(User::getId));

// 批量查询详情
List users = userMapper.selectBatchIds(ids.getRecords());

  1. 2. **使用存储过程**:将复杂分页逻辑封装到数据库存储过程
  2. # 五、最佳实践建议
  3. 1. **Wrapper复用**:对于频繁使用的查询条件,建议封装为静态方法
  4. ```java
  5. public class UserWrappers {
  6. public static LambdaQueryWrapper<User> activeUser() {
  7. return Wrappers.lambdaQuery()
  8. .eq(User::getStatus, 1)
  9. .gt(User::getCreateTime, LocalDateTime.now().minusYears(1));
  10. }
  11. }
  1. 查询缓存:对不常变动的查询结果使用本地缓存
  2. 字段过滤:使用select()方法指定返回字段,减少数据传输量

    1. wrapper.select(User::getId, User::getName);
  3. 动态表名:通过TableNameHandler实现分表查询

  4. SQL监控:集成日志框架监控慢查询

六、常见问题处理

  1. 分页总数不准确:检查是否有触发optimizeJoin优化的条件
  2. Wrapper条件失效:确认是否在Service层手动调用了clear()方法
  3. 多数据源分页:确保每个数据源都配置了分页插件
  4. Oracle分页:需额外配置DialectModel为oracle分页模式

通过系统掌握这些查询方法,开发者可以显著提升数据访问层的开发效率,构建出高性能、易维护的持久层代码。在实际项目中,建议结合业务场景选择合适的查询方式,并注意SQL性能优化和安全防护。