一、核心概念

  1. 嵌套查询
    • 子查询(IN/NOT IN/EXISTS)
    • 关联表嵌套查询
  2. 函数调用
    • SQL 函数(CONCAT/COUNT/SUM)
    • 自定义函数
  3. 条件构造器
    • QueryWrapper/LambdaQueryWrapper
    • 链式调用与条件组合

二、详细操作步骤

1. 嵌套查询实现
方式一:使用 inSql/notInSql
// 子查询:查询部门ID在财务部或技术部的员工
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.inSql("dept_id", 
    "SELECT id FROM department WHERE name IN ('财务部','技术部')"
);

// 等效SQL: 
// SELECT * FROM employee 
// WHERE dept_id IN (SELECT id FROM department WHERE name IN ('财务部','技术部'))
方式二:使用 exists/notExists
// 存在性查询:有订单的员工
wrapper.exists("SELECT 1 FROM orders o WHERE o.employee_id = employee.id");

// 等效SQL:
// SELECT * FROM employee e 
// WHERE EXISTS (SELECT 1 FROM orders o WHERE o.employee_id = e.id)
方式三:Lambda 嵌套查询
LambdaQueryWrapper<Employee> lambdaWrapper = Wrappers.lambdaQuery();
lambdaWrapper.inSql(Employee::getDeptId, 
    "SELECT id FROM department WHERE status = 1"
);
2. 函数调用实现
方式一:apply() 方法
// 调用日期函数
wrapper.apply("DATE_FORMAT(create_time,'%Y-%m-%d') = '2023-05-01'");

// 字符串拼接
wrapper.apply("CONCAT(first_name, last_name) LIKE {0}", "%张伟%");
方式二:func() 方法(Lambda)
lambdaWrapper.func(i -> {
    if (useFullName) {
        i.apply("CONCAT(first_name, last_name) LIKE {0}", "%张伟%");
    } else {
        i.like(Employee::getFirstName, "张");
    }
});
方式三:聚合函数
// 分组统计
QueryWrapper<Department> groupWrapper = new QueryWrapper<>();
groupWrapper.select("dept_id", "COUNT(*) as emp_count")
            .groupBy("dept_id")
            .having("emp_count > 10");
3. 条件构造器高级用法
复杂条件组合
wrapper.nested(w -> w.like("name", "张").or().like("name", "王"))
       .and(w -> w.gt("age", 25).lt("age", 40))
       .orderByDesc("salary");

// 等效SQL:
// WHERE (name LIKE '%张%' OR name LIKE '%王%') 
//   AND (age > 25 AND age < 40)
// ORDER BY salary DESC
动态条件构建
public List<Employee> dynamicQuery(String name, Integer minAge, Integer maxAge) {
    return lambdaWrapper
        .like(StringUtils.isNotBlank(name), Employee::getName, name)
        .ge(minAge != null, Employee::getAge, minAge)
        .le(maxAge != null, Employee::getAge, maxAge)
        .list();
}
联表查询条件
// 多表关联条件
wrapper.eq("department.status", 1)
       .like("department.name", "技术")
       .inSql("employee.id", 
           "SELECT employee_id FROM project WHERE status = 1"
       );

三、常见错误与解决

  1. 嵌套查询结果集过大

    • 错误:IN 子查询返回超过1000条记录
    • 解决:改用 JOIN 或分批次查询
  2. SQL注入风险

    • 错误:apply("CONCAT(name, '"+input+"')")
    • 解决:使用预编译占位符 apply("CONCAT(name, {0})", input)
  3. 函数兼容性问题

    • 错误:DATE_FORMAT 在Oracle中不兼容
    • 解决:使用数据库方言或MyBatis-Plus的 DbType 配置
  4. 条件顺序错误

    • 错误:or() 连接符位置不当导致逻辑错误
    • 解决:使用 nested() 明确条件分组

四、注意事项

  1. 性能敏感操作

    • 避免在循环中构建查询条件(每次 new QueryWrapper()
    • 大数据量 IN 查询改用 JOIN
  2. 跨数据库兼容

    • 函数调用使用 AbstractSqlParser 处理方言差异
    • 分页查询避免 SELECT *
  3. 空值处理

    • 使用 condition 参数自动跳过空条件:
      wrapper.like(StringUtils.isNotBlank(name), "name", name)
      
  4. XML 与注解优先级

    • 条件构造器优先级高于 XML 中的 <where>

五、使用技巧

  1. 链式条件中断

    wrapper.eq(...)
           .or()  // 注意:此后条件会与前面所有条件OR
           .gt(...);
    
  2. 条件复用

    Consumer<QueryWrapper<Employee>> commonCondition = w -> 
         w.eq("status", 1).gt("salary", 10000);
    
    wrapper.nested(commonCondition).like("name", "张");
    
  3. 动态表名+条件构造器

    wrapper.eq("tenant_id", TenantContext.getId())
           .apply("${tableName}", getDynamicTable());
    
  4. 自定义SQL片段

    @Select("SELECT * FROM employee ${ew.customSqlSegment}")
    List<Employee> selectByWrapper(@Param(Constants.WRAPPER) Wrapper wrapper);
    

六、最佳实践与性能优化

  1. 索引优化策略

    • 条件顺序:高筛选度字段在前
    • 避免函数包裹索引字段:WHERE YEAR(create_time)=2023WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
  2. 分页性能优化

    // 优化COUNT SQL
    Page<Employee> page = new Page<>(1, 10).setOptimizeCountSql(false);
    page.setSearchCount(false); // 不执行COUNT查询
    
  3. 批处理嵌套查询

    // 分批处理IN查询 (1000条/批)
    List<Long> ids = largeIdList; 
    List<List<Long>> partitions = Lists.partition(ids, 1000);
    
    partitions.forEach(batch -> 
         wrapper.or().in("id", batch)
    );
    
  4. 查询结果流式处理

    try (Cursor<Employee> cursor = mapper.selectCursor(wrapper)) {
         cursor.forEach(entity -> process(entity));
    }
    
  5. 二级缓存整合

    @CacheNamespace(implementation = MybatisRedisCache.class)
    public interface EmployeeMapper extends BaseMapper<Employee> {}
    

七、复杂场景综合示例

场景:查询技术部薪资TOP10的员工
LambdaQueryWrapper<Employee> wrapper = Wrappers.lambdaQuery();
wrapper.select(Employee::getId, Employee::getName, Employee::getSalary)
       .inSql(Employee::getDeptId, 
           "SELECT id FROM department WHERE name = '技术部'")
       .apply("salary > (SELECT AVG(salary) FROM employee)")
       .orderByDesc(Employee::getSalary)
       .last("LIMIT 10");

// 等效SQL:
// SELECT id, name, salary FROM employee
// WHERE dept_id IN (SELECT id FROM department WHERE name = '技术部')
//   AND salary > (SELECT AVG(salary) FROM employee)
// ORDER BY salary DESC
// LIMIT 10

八、调试与监控

  1. SQL输出配置

    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
  2. 性能分析插件

    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor interceptor = new PerformanceInterceptor();
        interceptor.setMaxTime(1000); // SQL超时阈值(ms)
        interceptor.setFormat(true);   // 格式化SQL
        return interceptor;
    }
    
  3. Explain分析

    wrapper.last("EXPLAIN FORMAT=JSON");
    String explainResult = mapper.selectOne(wrapper).toString();
    

通过掌握这些高级技巧,可应对:

  • 多层级嵌套查询
  • 动态函数调用
  • 复杂条件组合
  • 大数据量性能优化
  • 跨数据库兼容方案

关键要点

  1. 优先使用 Lambda 表达式避免字段硬编码
  2. 嵌套查询用 nested() 明确条件分组
  3. 函数调用用 apply() 保证预编译安全
  4. 大数据场景采用分批查询+流式处理