一、核心概念
LambdaUpdateWrapper<T>
是 MyBatis-Plus 提供的、基于 Lambda 表达式的类型安全条件构造器,专门用于构建 UPDATE
语句的 SET 和 WHERE 子句。
- 泛型
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");
}
三、常见错误
实体类缺少 Getter/Setter 方法:
- 错误: 实体类
User
没有getName()
或setName()
方法。 - 后果: 在使用
User::getName
时,编译器会报错Cannot resolve method 'getName' in 'User'
。 - 解决: 确保实体类为所有需要在
LambdaUpdateWrapper
中使用的字段提供标准的 Getter 和 Setter 方法。使用 Lombok 的@Data
注解可以简化。
- 错误: 实体类
在
update
方法中错误地传入 Entity:- 错误:
userMapper.update(user, lambdaUpdateWrapper);
其中user
对象有非 null 字段。 - 后果:
user
对象的非 null 字段也会被加入SET
子句,可能导致意外覆盖,逻辑混乱。 - 解决: 当
LambdaUpdateWrapper
定义了SET
子句时,update
方法的第一个参数必须传null
。
- 错误:
忽略
set
的返回值或链式调用中断:- 错误:
LambdaUpdateWrapper<User> w = new LambdaUpdateWrapper<>(); w.set(User::getName, "John"); // 没有链式调用或重新赋值 w.eq(User::getId, 1L);
- 后果: 代码逻辑正确,但没有利用链式调用的优势,略显冗余。如果误以为
set
修改了原对象引用(它返回的是this
),可能会导致误解。 - 解决: 养成链式调用的习惯,或者明确接收返回值(虽然通常不需要)。
- 错误:
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);
。
- 错误:
更新后未检查返回值:
- 错误: 执行
update
后不检查返回的int
值。 - 后果: 无法得知更新是否真正发生。例如,根据 ID 更新,但该 ID 不存在,返回 0,程序可能继续执行后续逻辑,导致数据不一致。
- 解决: 根据业务逻辑检查返回值。例如,期望更新1行,应检查
rowsAffected == 1
,否则抛出异常或记录日志。
- 错误: 执行
四、注意事项
- 编译时检查是核心优势: 充分利用
LambdaUpdateWrapper
的类型安全特性,它能及早发现字段名错误。 update
方法参数: 再次强调,当LambdaUpdateWrapper
负责SET
时,BaseMapper.update(entity, wrapper)
的entity
参数应为null
。- 事务管理: 更新操作务必在
@Transactional
注解的方法中执行,以保证数据一致性。 - 返回值处理:
update
方法返回受影响的行数,是重要的反馈信息,应在业务逻辑中合理处理。 - 并发控制: 高并发下考虑使用
@Version
乐观锁注解。 - SQL 注入防护:
LambdaUpdateWrapper
的所有条件方法(eq
,set
等)都使用PreparedStatement
参数化查询,天然防止 SQL 注入。这是比UpdateWrapper
更安全的地方。但是,last()
和apply()
方法如果拼接了用户输入,仍有风险,需谨慎。 - 性能:
LambdaUpdateWrapper
在运行时需要通过反射解析 Lambda 表达式获取字段名,有轻微的性能开销,但通常可以忽略不计。其带来的安全性和可维护性收益远大于这点开销。
五、使用技巧
- 链式调用: 这是
LambdaUpdateWrapper
的标准用法,让代码简洁明了。lambdaUpdateWrapper.set(User::getStatus, 1) .set(User::getUpdateTime, LocalDateTime.now()) .eq(User::getId, id) .gt(User::getVersion, currentVersion);
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());
nested()
方法: 用于创建括号( )
,明确逻辑优先级,尤其是在OR
和AND
混合时。// (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);
and(Consumer<Wrapper<T>> consumer)
/or(Consumer<Wrapper<T>> consumer)
: 可以在and
或or
后面直接嵌入一个Wrapper
的构建逻辑。lambdaUpdateWrapper.eq(User::getStatus, 1) .and(iw -> iw.gt(User::getAge, 18).lt(User::getAge, 65)); // AND (age > 18 AND age < 65)
last()
方法: 将 SQL 片段添加到 SQL 末尾。慎用,破坏可移植性,last("LIMIT 1")
可能有用,但要小心。apply()
方法: 拼接 SQL 片段,支持?
占位符(安全)。// 安全地使用数据库函数 lambdaUpdateWrapper.apply("date_format({0}, '%Y-%m') = {1}", User::getCreateTime, "2023-10");
- 与
QueryWrapper
互转: 可以通过lambdaUpdateWrapper.getWrapper()
获取其内部的Wrapper
,但通常不需要。
六、最佳实践与性能优化
- 首选
LambdaUpdateWrapper
: 在所有需要构建复杂更新条件的场景下,优先选择LambdaUpdateWrapper
,享受类型安全带来的开发效率和稳定性。 - 精确的 WHERE 条件: 始终确保
WHERE
子句足够精确,避免全表扫描。利用主键、唯一索引或高选择性的字段。 - 利用数据库索引: 确保
WHERE
子句中使用的字段(尤其是等值、范围查询字段)有合适的数据库索引。 - 批量更新考虑:
- 如果更新多条记录且每条记录的更新值不同,考虑使用 MyBatis-Plus 的
Service
层的updateBatchById(List<T> entityList)
方法(基于主键)。 - 如果更新多条记录且更新值相同,使用
LambdaUpdateWrapper
配合in
条件是高效的选择。
- 如果更新多条记录且每条记录的更新值不同,考虑使用 MyBatis-Plus 的
- 避免大事务: 对于大范围更新,考虑分页或分批处理,减少单次事务的锁持有时间。
- 结合自动填充: 充分利用
@TableField(fill = FieldFill.INSERT_UPDATE)
等注解,让框架自动管理update_time
等字段,减少手动设置和出错可能。 - 日志监控: 开启 MyBatis SQL 日志,观察生成的 SQL 语句,验证条件是否正确,检查执行计划。
- 处理更新结果: 根据业务需求,检查
update
方法的返回值。例如,更新单条记录时,期望返回 1,若返回 0 则说明记录不存在或条件不匹配,应有相应处理。 - 简单更新用
updateById
: 如果只是根据主键更新实体对象中所有非 null 字段,直接使用userMapper.updateById(user)
最简单高效,无需LambdaUpdateWrapper
。
总结: LambdaUpdateWrapper
是 MyBatis-Plus 中进行条件更新的现代化、类型安全的工具。通过本教程的详细步骤和实践指导,您应该能够熟练地使用它来构建安全、高效、可维护的数据库更新逻辑。记住,类型安全、精确条件、事务管理和结果验证是编写高质量更新代码的关键。