一、核心概念

LambdaQueryWrapper 是 MyBatis-Plus 提供的查询条件构造器,通过 Lambda 表达式避免硬编码字段名,提升代码可读性和安全性。

  • 优势
    • 类型安全:编译时检查字段名是否存在。
    • 智能提示:IDE 自动补全实体类属性。
    • 防误写:避免手写 "name" 导致的拼写错误。

二、详细操作步骤(以查询用户为例)

1. 引入依赖(Maven)

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0+</version>
</dependency>

2. 创建实体类

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

3. 创建 Mapper 接口

public interface UserMapper extends BaseMapper<User> {}

4. 使用 LambdaQueryWrapper 构建查询

场景1:基础查询(name = "John" 且 age > 18)

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "John")   // 等价于 SQL: name = 'John'
       .gt(User::getAge, 18)        // age > 18
       .select(User::getId, User::getName); // 只查询 id 和 name

List<User> userList = userMapper.selectList(wrapper);

场景2:复杂条件(OR 嵌套)

wrapper.nested(wq -> wq.eq(User::getAge, 20).or().eq(User::getAge, 25))
       .likeRight(User::getEmail, "admin@"); // email 以 "admin@" 开头

场景3:排序与分页

wrapper.orderByDesc(User::getAge) // 按年龄倒序
       .last("LIMIT 10");        // 手动拼接 SQL(慎用!)

// 配合分页插件
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
Page<User> result = userMapper.selectPage(page, wrapper);

三、常见错误与解决

  1. NPE(NullPointerException)

    • 原因User::getName 可能被误写为 User::getName()(多加了括号)。
    • 解决:正确写法 User::getName
  2. 字段不存在错误

    • 原因:Lambda 表达式引用非实体类属性(如 User::getAddress 但实体无此字段)。
    • 解决:检查实体类属性名与方法引用一致性。
  3. 误用函数式参数

    // 错误!条件被覆盖
    wrapper.eq(user.getId() != null, User::getId, user.getId())
    // 正确:先判空再构建条件
    if (user.getId() != null) {
        wrapper.eq(User::getId, user.getId());
    }
    

四、注意事项

  1. 避免 last() 注入风险
    禁止将用户输入直接拼接进 last()

    // 危险写法!
    wrapper.last("ORDER BY " + userInput);
    // 安全替代:使用 wrapper.orderBy() 系列方法
    
  2. 链式调用中断
    每次条件构造后返回新对象,连续操作需保持链式:

    // 错误!丢失后续条件
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(...);
    wrapper.gt(...); // 正确:应连续调用
    

五、使用技巧

1. 动态条件拼接

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name)
       .between(ageMin != null && ageMax != null, User::getAge, ageMin, ageMax);

2. 复用 Wrapper

public LambdaQueryWrapper<User> buildQuery(String name, Integer age) {
    return new LambdaQueryWrapper<User>()
           .eq(User::getName, name)
           .gt(User::getAge, age);
}

3. 快速选择字段

wrapper.select(User.class, 
    info -> !info.getColumn().equals("password")); // 排除敏感字段

六、最佳实践与性能优化

  1. 索引友好查询

    • 避免在索引列上使用 !=not like
    • LIKE 查询尽量用 likeRight("abc%")(前缀匹配)。
  2. 批量操作优化
    使用 inSql() 代替循环多次查询:

    // 低效:循环查询
    for (Long id : idList) {
        mapper.selectById(id);
    }
    // 高效:单次查询
    wrapper.in(User::getId, idList); 
    
  3. 关联查询处理

    • 复杂关联查询建议用 XML/注解 SQL。
    • 简单关联可用 apply() 拼接 JOIN 语句(需谨慎)。

七、完整示例代码

// 查询邮箱包含 "example" 且年龄在 20-30 之间的用户,按 ID 倒序
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getEmail, "example")
       .between(User::getAge, 20, 30)
       .orderByDesc(User::getId);

List<User> users = userMapper.selectList(wrapper);

总结

关键点 行动指南
类型安全 坚持使用 User::getName 而非 "name"
动态条件 善用 eq(condition, ...) 避免 if 嵌套
性能敏感操作 优先使用 in() 替代循环单条查询
SQL 注入防范 禁止未校验参数传入 last() / apply()

掌握 LambdaQueryWrapper 可大幅提升 MyBatis-Plus 开发效率,结合 Lambda 表达式的优雅性与条件构造的灵活性,能写出更健壮、易维护的数据库操作代码。