一、核心概念

LambdaUpdateWrapper<T> 是 MyBatis-Plus 提供的、基于 Lambda 表达式的类型安全条件构造器,专门用于构建 UPDATE 语句的 SETWHERE 子句。

  • 泛型 T: 代表您要操作的实体类类型(Entity Class)。
  • 类型安全 (Type Safety): 这是 LambdaUpdateWrapper 的最大优势。它通过 实体类::字段名 (如 User::getName) 的 Lambda 表达式来引用字段。这使得字段名在编译期就被检查,一旦实体类中没有该字段或字段名拼写错误,编译就会失败,避免了运行时因字段名错误导致的 SQL 错误或更新失败。
  • SET 子句: 使用 set(字段引用, 新值) 或其变体来指定要更新的字段和值。
  • WHERE 子句: 使用 eq(字段引用, 值), ne, gt, ge, lt, le, like, in, isNull, isNotNull 等方法,结合 Lambda 字段引用来构建查询条件。
  • 链式调用: 与 UpdateWrapper 一样,所有方法均支持流畅的链式调用。
  • UpdateWrapper 的关系: LambdaUpdateWrapper 本质上是 UpdateWrapper 的增强版,内部会将 Lambda 表达式解析为对应的数据库字段名。您可以将 UpdateWrapper 通过 .lambda() 方法转换为 LambdaUpdateWrapper

二、详细操作步骤

以下是使用 LambdaUpdateWrapper 进行数据库更新的完整、详细步骤:

步骤 1: 引入依赖

确保您的项目中已正确引入 MyBatis-Plus 的依赖。LambdaUpdateWrapper 是 MyBatis-Plus 的核心功能,无需额外依赖。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version> <!-- 请使用最新稳定版本 -->
</dependency>

步骤 2: 创建实体类 (Entity)

定义一个与数据库表对应的 Java 实体类。LambdaUpdateWrapper 依赖于实体类的字段和 Getter/Setter 方法。

import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@TableName("user") // 指定对应的数据库表名
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO) // 主键
    private Long id;

    @TableField("name")
    private String name;

    @TableField("age")
    private Integer age;

    @TableField("email")
    private String email;

    @TableField("status")
    private Integer status;

    @TableField(fill = FieldFill.INSERT) // 创建时间,插入时填充
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE) // 更新时间,插入和更新时填充
    private LocalDateTime updateTime;

    // 必须提供无参构造函数
    public User() {}

    // 可选:提供有参构造函数
    public User(String name, Integer age, String email, Integer status) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.status = status;
    }

    // --- Getter 和 Setter 方法 (LambdaUpdateWrapper 需要它们) ---
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }

    public LocalDateTime getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(LocalDateTime updateTime) {
        this.updateTime = updateTime;
    }
}

步骤 3: 创建 Mapper 接口

创建一个继承自 BaseMapper<T> 的 Mapper 接口。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // BaseMapper 提供了 update 方法
}

步骤 4: 在 Service 或 Controller 中使用 LambdaUpdateWrapper

这是核心步骤。以下是几种常见的更新场景:

场景 1: 根据 ID 更新特定字段 (最常见)

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户ID更新用户的姓名和邮箱
     * @param userId 用户ID
     * @param newName 新姓名
     * @param newEmail 新邮箱
     * @return 更新成功的记录数
     */
    @Transactional // 确保操作在事务中执行
    public int updateUserById(Long userId, String newName, String newEmail) {
        // 1. 创建 LambdaUpdateWrapper 实例,指定实体类型
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

        // 2. 设置要更新的字段 (SET 子句)
        // set(字段引用, 新值): 使用 User::getName, User::getEmail 等方法引用
        lambdaUpdateWrapper.set(User::getName, newName);
        lambdaUpdateWrapper.set(User::getEmail, newEmail);
        // 注意:这里不会自动更新 updateTime,除非在实体上配置了 FieldFill 或手动设置
        // lambdaUpdateWrapper.set(User::getUpdateTime, LocalDateTime.now()); // 如果需要手动更新时间

        // 3. 设置更新条件 (WHERE 子句)
        // eq(字段引用, 值): 字段引用 = 值
        lambdaUpdateWrapper.eq(User::getId, userId);

        // 4. 执行更新操作
        // userMapper.update(entity, wrapper)
        // 关键:当 SET 由 wrapper 定义时,第一个参数 entity 传 null
        int rowsAffected = userMapper.update(null, lambdaUpdateWrapper);

        return rowsAffected;
    }
}

场景 2: 根据复杂条件更新多个记录

/**
 * 将年龄大于等于 minAge 且状态为 status 的用户的状态更新为 newStatus
 * @param minAge 最小年龄
 * @param status 原状态
 * @param newStatus 新状态
 * @return 更新成功的记录数
 */
@Transactional
public int updateUsersByAgeAndStatus(Integer minAge, Integer status, Integer newStatus) {
    // 1. 创建 LambdaUpdateWrapper
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

    // 2. 设置要更新的字段
    lambdaUpdateWrapper.set(User::getStatus, newStatus);

    // 3. 构建复杂的 WHERE 条件 (链式调用)
    // ge(字段引用, 值): 字段引用 >= 值
    // and(): 添加 AND 条件 (通常可省略,因为连续调用默认是 AND)
    // eq(字段引用, 值): 字段引用 = 值
    lambdaUpdateWrapper.ge(User::getAge, minAge)
                      .eq(User::getStatus, status);
    // 上面两行等同于:lambdaUpdateWrapper.ge(User::getAge, minAge).and().eq(User::getStatus, status);
    // 因为连续的条件默认是 AND 连接,所以 .and() 通常可以省略。

    // 4. 执行更新
    return userMapper.update(null, lambdaUpdateWrapper);
}

场景 3: 结合 or 条件更新

/**
 * 将姓名为 name1 或 name2 的用户的状态更新为 newStatus
 * @param name1 姓名1
 * @param name2 姓名2
 * @param newStatus 新状态
 * @return 更新成功的记录数
 */
@Transactional
public int updateUsersByNameOr(String name1, String name2, Integer newStatus) {
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

    lambdaUpdateWrapper.set(User::getStatus, newStatus);

    // 使用 or() 方法添加 OR 条件
    // 注意:or() 会作用于它前面的所有条件,通常需要结合 nested() 使用括号明确优先级
    // 方式一:使用 nested() 创建括号组 (推荐,清晰)
    lambdaUpdateWrapper.nested(iw -> iw.eq(User::getName, name1).or().eq(User::getName, name2));

    // 方式二:直接使用 or() (逻辑上等价,但不如 nested() 清晰)
    // lambdaUpdateWrapper.eq(User::getName, name1).or().eq(User::getName, name2);

    return userMapper.update(null, lambdaUpdateWrapper);
    // 生成的 SQL 类似:UPDATE user SET status=? WHERE (name = ? OR name = ?)
}

圔景 4: 动态条件更新 (推荐做法)

/**
 * 根据可选条件动态更新用户信息
 * @param userId 用户ID (必须)
 * @param userUpdateDTO 包含可选更新字段的 DTO
 * @return 更新成功的记录数
 */
@Transactional
public int updateUserDynamically(Long userId, UserUpdateDTO userUpdateDTO) {
    // UserUpdateDTO 示例:
    // public class UserUpdateDTO {
    //     private String name;
    //     private Integer age;
    //     private String email;
    //     // getters and setters
    // }

    // 1. 创建 LambdaUpdateWrapper
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();

    // 2. 动态添加 SET 字段 (利用 set 的条件版本)
    // set(boolean condition, 字段引用, 新值): 只有当 condition 为 true 时才添加此 SET 子句
    lambdaUpdateWrapper.set(userUpdateDTO.getName() != null, User::getName, userUpdateDTO.getName())
                      .set(userUpdateDTO.getAge() != null, User::getAge, userUpdateDTO.getAge())
                      .set(userUpdateDTO.getEmail() != null, User::getEmail, userUpdateDTO.getEmail());

    // 3. 设置主键条件
    lambdaUpdateWrapper.eq(User::getId, userId);

    // 4. 执行更新
    return userMapper.update(null, lambdaUpdateWrapper);
}

// 调用示例:
// UserUpdateDTO dto = new UserUpdateDTO();
// dto.setName("NewName");
// // dto.setAge(null); // 年龄不更新
// int result = userService.updateUserDynamically(1L, dto); // 只更新 name 字段

场景 5: 与 UpdateWrapper 互操作

/**
 * 演示 UpdateWrapper 与 LambdaUpdateWrapper 的转换
 */
public void wrapperConversionExample() {
    // 从 UpdateWrapper 转换为 LambdaUpdateWrapper
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.eq("status", 1); // 使用字符串
    // 转换
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = updateWrapper.lambda();
    lambdaUpdateWrapper.set(User::getName, "ConvertedName"); // 现在可以使用 Lambda
    // ... 继续使用 lambdaUpdateWrapper

    // 直接创建 LambdaUpdateWrapper 是更常见的做法
    LambdaUpdateWrapper<User> directLambdaWrapper = new LambdaUpdateWrapper<>();
    directLambdaWrapper.eq(User::getStatus, 1).set(User::getName, "DirectName");
}

三、常见错误

  1. 实体类缺少 Getter/Setter 方法:

    • 错误: 实体类 User 没有 getName()setName() 方法。
    • 后果: 在使用 User::getName 时,编译器会报错 Cannot resolve method 'getName' in 'User'
    • 解决: 确保实体类为所有需要在 LambdaUpdateWrapper 中使用的字段提供标准的 Getter 和 Setter 方法。使用 Lombok 的 @Data 注解可以简化。
  2. update 方法中错误地传入 Entity:

    • 错误: userMapper.update(user, lambdaUpdateWrapper); 其中 user 对象有非 null 字段。
    • 后果: user 对象的非 null 字段也会被加入 SET 子句,可能导致意外覆盖,逻辑混乱。
    • 解决: LambdaUpdateWrapper 定义了 SET 子句时,update 方法的第一个参数必须传 null
  3. 忽略 set 的返回值或链式调用中断:

    • 错误:
      LambdaUpdateWrapper<User> w = new LambdaUpdateWrapper<>();
      w.set(User::getName, "John"); // 没有链式调用或重新赋值
      w.eq(User::getId, 1L);
      
    • 后果: 代码逻辑正确,但没有利用链式调用的优势,略显冗余。如果误以为 set 修改了原对象引用(它返回的是 this),可能会导致误解。
    • 解决: 养成链式调用的习惯,或者明确接收返回值(虽然通常不需要)。
  4. or 条件优先级错误:

    • 错误: wrapper.eq(A).or().eq(B).eq(C); 期望 (A OR B) AND C,但实际是 A OR (B AND C)
    • 后果: 条件逻辑错误,更新了不该更新的记录。
    • 解决: 使用 nested() 方法明确创建括号组:wrapper.nested(iw -> iw.eq(A).or().eq(B)).eq(C);
  5. 更新后未检查返回值:

    • 错误: 执行 update 后不检查返回的 int 值。
    • 后果: 无法得知更新是否真正发生。例如,根据 ID 更新,但该 ID 不存在,返回 0,程序可能继续执行后续逻辑,导致数据不一致。
    • 解决: 根据业务逻辑检查返回值。例如,期望更新1行,应检查 rowsAffected == 1,否则抛出异常或记录日志。

四、注意事项

  1. 编译时检查是核心优势: 充分利用 LambdaUpdateWrapper 的类型安全特性,它能及早发现字段名错误。
  2. update 方法参数: 再次强调,当 LambdaUpdateWrapper 负责 SET 时,BaseMapper.update(entity, wrapper)entity 参数应为 null
  3. 事务管理: 更新操作务必在 @Transactional 注解的方法中执行,以保证数据一致性。
  4. 返回值处理: update 方法返回受影响的行数,是重要的反馈信息,应在业务逻辑中合理处理。
  5. 并发控制: 高并发下考虑使用 @Version 乐观锁注解。
  6. SQL 注入防护: LambdaUpdateWrapper 的所有条件方法(eq, set 等)都使用 PreparedStatement 参数化查询,天然防止 SQL 注入。这是比 UpdateWrapper 更安全的地方。但是last()apply() 方法如果拼接了用户输入,仍有风险,需谨慎。
  7. 性能: LambdaUpdateWrapper 在运行时需要通过反射解析 Lambda 表达式获取字段名,有轻微的性能开销,但通常可以忽略不计。其带来的安全性和可维护性收益远大于这点开销。

五、使用技巧

  1. 链式调用: 这是 LambdaUpdateWrapper 的标准用法,让代码简洁明了。
    lambdaUpdateWrapper.set(User::getStatus, 1)
                      .set(User::getUpdateTime, LocalDateTime.now())
                      .eq(User::getId, id)
                      .gt(User::getVersion, currentVersion);
    
  2. set 的条件版本: set(boolean condition, SFunction<T, ?> column, Object val) 是实现动态更新的利器。
    lambdaUpdateWrapper.set(dto.getName() != null, User::getName, dto.getName())
                      .set(dto.getAge() != null, User::getAge, dto.getAge());
    
  3. nested() 方法: 用于创建括号 ( ),明确逻辑优先级,尤其是在 ORAND 混合时。
    // (name = 'John' OR name = 'Jane') AND status = 1
    lambdaUpdateWrapper.nested(iw -> iw.eq(User::getName, "John").or().eq(User::getName, "Jane"))
                      .eq(User::getStatus, 1);
    
  4. and(Consumer<Wrapper<T>> consumer) / or(Consumer<Wrapper<T>> consumer): 可以在 andor 后面直接嵌入一个 Wrapper 的构建逻辑。
    lambdaUpdateWrapper.eq(User::getStatus, 1)
                      .and(iw -> iw.gt(User::getAge, 18).lt(User::getAge, 65)); // AND (age > 18 AND age < 65)
    
  5. last() 方法: 将 SQL 片段添加到 SQL 末尾。慎用,破坏可移植性,last("LIMIT 1") 可能有用,但要小心。
  6. apply() 方法: 拼接 SQL 片段,支持 ? 占位符(安全)。
    // 安全地使用数据库函数
    lambdaUpdateWrapper.apply("date_format({0}, '%Y-%m') = {1}", User::getCreateTime, "2023-10");
    
  7. QueryWrapper 互转: 可以通过 lambdaUpdateWrapper.getWrapper() 获取其内部的 Wrapper,但通常不需要。

六、最佳实践与性能优化

  1. 首选 LambdaUpdateWrapper: 在所有需要构建复杂更新条件的场景下,优先选择 LambdaUpdateWrapper,享受类型安全带来的开发效率和稳定性。
  2. 精确的 WHERE 条件: 始终确保 WHERE 子句足够精确,避免全表扫描。利用主键、唯一索引或高选择性的字段。
  3. 利用数据库索引: 确保 WHERE 子句中使用的字段(尤其是等值、范围查询字段)有合适的数据库索引。
  4. 批量更新考虑:
    • 如果更新多条记录且每条记录的更新值不同,考虑使用 MyBatis-Plus 的 Service 层的 updateBatchById(List<T> entityList) 方法(基于主键)。
    • 如果更新多条记录且更新值相同,使用 LambdaUpdateWrapper 配合 in 条件是高效的选择。
  5. 避免大事务: 对于大范围更新,考虑分页或分批处理,减少单次事务的锁持有时间。
  6. 结合自动填充: 充分利用 @TableField(fill = FieldFill.INSERT_UPDATE) 等注解,让框架自动管理 update_time 等字段,减少手动设置和出错可能。
  7. 日志监控: 开启 MyBatis SQL 日志,观察生成的 SQL 语句,验证条件是否正确,检查执行计划。
  8. 处理更新结果: 根据业务需求,检查 update 方法的返回值。例如,更新单条记录时,期望返回 1,若返回 0 则说明记录不存在或条件不匹配,应有相应处理。
  9. 简单更新用 updateById: 如果只是根据主键更新实体对象中所有非 null 字段,直接使用 userMapper.updateById(user) 最简单高效,无需 LambdaUpdateWrapper

总结: LambdaUpdateWrapper 是 MyBatis-Plus 中进行条件更新的现代化、类型安全的工具。通过本教程的详细步骤和实践指导,您应该能够熟练地使用它来构建安全、高效、可维护的数据库更新逻辑。记住,类型安全、精确条件、事务管理和结果验证是编写高质量更新代码的关键。