MyBatis-Plus Wrapper 构造器

Administrator 0 阅读

Wrapper 就是一个用于构造 SQL 语句中 WHERE 条件部分的 Java 工具类。它让你能够通过链式调用的方式,用 Java 代码拼写出各种复杂的查询、更新、删除条件,从而避免手写繁琐的 SQL 片段。Wrapper 家族主要有两个分支:QueryWrapper(用于查询)和 UpdateWrapper(用于更新),以及它们的 Lambda 版本。


1. Wrapper 继承体系(了解即可)

text

Wrapper(顶级接口)
    └── AbstractWrapper(抽象类,实现了大部分条件方法)
            ├── QueryWrapper(用于查询、删除)
            ├── UpdateWrapper(用于更新,可以 set 字段)
            ├── LambdaQueryWrapper(Lambda 版 QueryWrapper)
            └── LambdaUpdateWrapper(Lambda 版 UpdateWrapper)

在实际开发中,我们通常直接使用 LambdaQueryWrapper 和 LambdaUpdateWrapper,因为它们能用 Lambda 表达式引用实体类的属性(比如 User::getName),避免写错字段名(字符串形式容易写错或重构时遗漏)。


2. 为什么需要 Wrapper?

假如我们要查一张用户表 user,需要满足以下条件:

  • 年龄在 18~30 之间

  • 性别为男

  • 姓名包含“张”

  • 按年龄降序排序

不用 Wrapper 的话,你需要在 Mapper.xml 里手写 SQL:

xml

<select id="selectUserList" resultType="User">
    SELECT * FROM user 
    WHERE age BETWEEN 18 AND 30 
      AND gender = '男' 
      AND name LIKE '%张%'
    ORDER BY age DESC
</select>

如果使用 Wrapper,你不用写 XML,直接在 Java 代码里构造:

java

// 创建条件构造器
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.between(User::getAge, 18, 30)
       .eq(User::getGender, "男")
       .like(User::getName, "张")
       .orderByDesc(User::getAge);

// 直接传入 Mapper 方法执行查询
List<User> users = userMapper.selectList(wrapper);

是不是简洁很多?而且完全避免了 XML 的维护工作。


3. 核心常用方法(以 LambdaQueryWrapper 为例)

🔹 比较条件

方法

说明

示例

eq

等于 =

wrapper.eq(User::getAge, 18) → age = 18

ne

不等于 <>

wrapper.ne(User::getAge, 18) → age <> 18

gt

大于 >

wrapper.gt(User::getAge, 18) → age > 18

ge

大于等于 >=

wrapper.ge(User::getAge, 18)

lt

小于 <

wrapper.lt(User::getAge, 18)

le

小于等于 <=

wrapper.le(User::getAge, 18)

between

在区间内 (包含边界)

wrapper.between(User::getAge, 18, 30) → age BETWEEN 18 AND 30

notBetween

不在区间内

…

in

在集合/数组中

wrapper.in(User::getAge, Arrays.asList(18,20,22))

notIn

不在集合中

…

isNull

字段为 NULL

wrapper.isNull(User::getEmail)

isNotNull

字段不为 NULL

…

🔹 模糊匹配

方法

说明

示例

like

全模糊匹配

wrapper.like(User::getName, "张") → name LIKE '%张%'

likeLeft

左模糊

wrapper.likeLeft(User::getName, "张") → name LIKE '%张'

likeRight

右模糊

wrapper.likeRight(User::getName, "张") → name LIKE '张%'

🔹 排序

方法

说明

示例

orderByAsc

升序

wrapper.orderByAsc(User::getAge, User::getId) → ORDER BY age ASC, id ASC

orderByDesc

降序

wrapper.orderByDesc(User::getAge)

orderBy

指定顺序

wrapper.orderBy(true, true, User::getAge) 第一个参数是否排序,第二个是否升序

🔹 逻辑组合(AND / OR)

默认情况下,多个条件是 AND 连接。如果要使用 OR,可以调用 or() 方法。

AND 示例(默认) :

java

wrapper.eq(User::getGender, "男")
       .gt(User::getAge, 18);
// SQL: WHERE gender = '男' AND age > 18

OR 示例:

java

wrapper.eq(User::getGender, "男")
       .or()
       .eq(User::getGender, "女");
// SQL: WHERE gender = '男' OR gender = '女'

复杂 OR 嵌套(推荐用 lambda 表达式) :

java

wrapper.eq(User::getStatus, 1)
       .and(w -> w.eq(User::getAge, 18).or().eq(User::getAge, 20));
// SQL: WHERE status = 1 AND ( age = 18 OR age = 20 )

🔹 指定查询字段

java

wrapper.select(User::getId, User::getName, User::getAge);
// 只查询这三个字段,SQL 为 SELECT id,name,age FROM ...

🔹 分组和 having

java

wrapper.groupBy(User::getGender)
       .having("AVG(age) > {0}", 25);
// SQL: GROUP BY gender HAVING AVG(age) > 25

🔹 其他常用方法

  • last:直接拼接到 SQL 末尾(注意有 SQL 注入风险,谨慎使用)
    wrapper.last("limit 1")

  • exists / notExists:子查询
    wrapper.exists("select id from user_log where user_id = user.id")


4. QueryWrapper 与 LambdaQueryWrapper 的区别

  • QueryWrapper:字段名用字符串传入,容易写错,重构实体类时不会自动同步。
    java

    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.eq("name", "张三");   // 字符串,容易打错
  • LambdaQueryWrapper:用Lambda 方法引用,编译期就能检查,重构友好。
    java

    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    lqw.eq(User::getName, "张三");   // 推荐使用

强烈建议只用 Lambda 版本。


5. UpdateWrapper 的用法

UpdateWrapper 除了能构造 WHERE 条件,还能指定 SET 子句,常用于更新部分字段。

示例1:更新字段值(不通过实体对象)

java

// 将所有 age > 30 的用户的状态改为 0,并且年龄增加 1
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(User::getStatus, 0)
             .setSql("age = age + 1")   // 直接写 SQL 片段
             .gt(User::getAge, 30);

userMapper.update(null, updateWrapper);  // 第一个参数是实体,null表示只更新 set 指定的字段

示例2:条件更新结合实体

java

User user = new User();
user.setStatus(1);  // 要更新的字段

LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getName, "张三");

userMapper.update(user, wrapper);
// SQL: UPDATE user SET status = 1 WHERE name = '张三'

6. 动态条件:当某个条件为空时不拼接

Wrapper 的方法都提供了带 condition 参数的重载,让你可以控制是否添加该条件。这是开发中最常用的技巧。

java

// 假如前端传来参数:name(可为空),age(可为空)
public List<User> searchUser(String name, Integer age) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    // 只有当 name 不为空时,才拼接 name like 条件
    wrapper.like(StringUtils.hasText(name), User::getName, name);
    // 只有当 age 不为空时,才拼接 age = 条件
    wrapper.eq(age != null, User::getAge, age);
    return userMapper.selectList(wrapper);
}

这样当前端不传某个参数时,该条件就不会出现在 SQL 中,非常安全便捷。


7. 实战例子

例1:分页 + 动态条件 + 排序

java

@GetMapping("/list")
public IPage<User> listUsers(@RequestParam(required = false) String name,
                             @RequestParam(required = false) Integer minAge,
                             @RequestParam(defaultValue = "1") long current,
                             @RequestParam(defaultValue = "10") long size) {
    // 分页对象
    Page<User> page = new Page<>(current, size);
    // 构造条件
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(StringUtils.hasText(name), User::getName, name)
           .ge(minAge != null, User::getAge, minAge)
           .orderByDesc(User::getCreateTime);
    // 执行分页查询
    return userMapper.selectPage(page, wrapper);
}

例2:批量删除(根据多个 ID)

java

List<Long> idList = Arrays.asList(1L, 2L, 3L);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.in(User::getId, idList);
userMapper.delete(wrapper);
// 等价 SQL:DELETE FROM user WHERE id IN (1,2,3)

例3:复杂嵌套查询(年龄>20 并且 (性别男 或者 名字包含“张”))

java

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20)
       .and(w -> w.eq(User::getGender, "男")
                  .or()
                  .like(User::getName, "张"));

8. 注意事项与常见坑

  1. 不要混用 QueryWrapper 和 LambdaQueryWrapper,选定一种风格保持统一。

  2. 条件构造器只适用于单表操作。如果需要多表联查,要么手写 SQL(放在 Mapper.xml 里),要么使用 MP 的 @Select 注解或自定义 Mapper 方法,Wrapper 仍然可以作为参数传入(但需注意字段别名)。

  3. or() 方法会与前面的条件形成括号分组,小心逻辑错误。推荐使用 and(consumer) 或 or(consumer) 显式包裹。

  4. last("limit 1") 等方法会直接拼接到 SQL 末尾,可能与分页插件冲突,且有 SQL 注入风险,尽量用 wrapper.last("limit 1") 仅在测试或特定场景使用。

  5. UpdateWrapper 的 set 方法如果设置字段值为 null,数据库会更新为 null,除非你配置了字段策略(@TableField(strategy = FieldStrategy.IGNORED))。


总结

Wrapper 是 MyBatis-Plus 的灵魂工具,它让我们用 Java 代码优雅地构造 SQL 条件,极大简化了数据访问层的开发。记住三个核心点:

  • 用 LambdaQueryWrapper 和 LambdaUpdateWrapper

  • 善用带 condition 参数的方法实现动态条件

  • 复杂条件用 .and() / .or() 包裹 Lambda 表达式